文档章节

线程池实现爬虫

alazyer
 alazyer
发布于 2016/09/06 20:19
字数 705
阅读 66
收藏 0

偶然间学习实验楼课程, 看到的, 想到自己刚刚出来找工作的时候, 面试过一家公司让自己实现线程池, 并利用实现的线程池进行多线程爬取, 当时水平太菜(现在也很菜, 不过比那个时候好多了). 这里记录下, 当时没有理解的是queue.Queue().task_done(), 现在想想就是这个线程的任务处理完了, 通知queue.Queue(), 让他知道这个处理完了, 别的线程调用queue.Queue().get()时, 不会有这个处理过的element出现, 让其他线程处理.

这里将实验楼的代码贴过来, 权当留念了. 不过代码有好多可以改进或者说有更好的方式实现. 比如:

  • 使用requests而不是socket来爬取网站内容
  • 使用BeautifulSoup或者xpath来解析爬取的网页内容中的链接信息
from queue import Queue 
from threading import Thread, Lock
import urllib.parse
import socket
import re
import time

seen_urls = set(['/'])
lock = Lock()


class Fetcher(Thread):
    def __init__(self, tasks):
        Thread.__init__(self)
        self.tasks = tasks
        self.daemon = True

        self.start()

    def run(self):
        while True:
            url = self.tasks.get()
            print(url)
            sock = socket.socket()
            sock.connect(('localhost', 3000))
            get = 'GET {} HTTP/1.0\r\nHost: localhost\r\n\r\n'.format(url)
            sock.send(get.encode('ascii'))
            response = b''
            chunk = sock.recv(4096)
            while chunk:
                response += chunk
                chunk = sock.recv(4096)

            links = self.parse_links(url, response)

            lock.acquire()
            for link in links.difference(seen_urls):
                self.tasks.put(link)
            seen_urls.update(links)    
            lock.release()

            self.tasks.task_done()

    def parse_links(self, fetched_url, response):
        if not response:
            print('error: {}'.format(fetched_url))
            return set()
        if not self._is_html(response):
            return set()
        urls = set(re.findall(r'''(?i)href=["']?([^\s"'<>]+)''',
                              self.body(response)))

        links = set()
        for url in urls:
            normalized = urllib.parse.urljoin(fetched_url, url)
            parts = urllib.parse.urlparse(normalized)
            if parts.scheme not in ('', 'http', 'https'):
                continue
            host, port = urllib.parse.splitport(parts.netloc)
            if host and host.lower() not in ('localhost'):
                continue
            defragmented, frag = urllib.parse.urldefrag(parts.path)
            links.add(defragmented)

        return links

    def body(self, response):
        body = response.split(b'\r\n\r\n', 1)[1]
        return body.decode('utf-8')

    def _is_html(self, response):
        head, body = response.split(b'\r\n\r\n', 1)
        headers = dict(h.split(': ') for h in head.decode().split('\r\n')[1:])
        return headers.get('Content-Type', '').startswith('text/html')


class ThreadPool:
    def __init__(self, num_threads):
        self.tasks = Queue()
        for _ in range(num_threads):
            Fetcher(self.tasks)

    def add_task(self, url):
        self.tasks.put(url)

    def wait_completion(self):
        self.tasks.join()

if __name__ == '__main__':
    start = time.time()
    pool = ThreadPool(4)
    pool.add_task("/")
    pool.wait_completion()
    print('{} URLs fetched in {:.1f} seconds'.format(len(seen_urls),time.time() - start))

总结:

  • 线程池就是一个包含多个线程的容器, 内部有多个线程, 现成一直处于工作状态.
  • 线程处于工作状态的意思是说: 当任务队列中有任务时, 线程从任务队列中取得任务, 然后进行处理, 并将任务结果以合理的方式再次反馈到任务队列中; 当任务队列中没有任务时, 则阻塞, 等待其他线程向任务队列添加任务或者通过调用线程池的add_task添加初始任务.
  • 调用任务队列的join方法实现阻塞等待, 当所有任务都已经执行完成后(任务队列为空, 并且所有现成都处于阻塞状态), 输出统计信息.

内容都是个人理解, 如有出入, 请赐教.

© 著作权归作者所有

alazyer
粉丝 5
博文 68
码字总数 35105
作品 0
程序员
私信 提问
Memcache-LRU爬虫线程-源码分析

介绍 memcache 中实现了内存管理模型用来存储数据,而在此基础上又实现了一套LRU爬虫模型来维护这些已使用的内存,因为如果一直使用这些内存而不去维护会占用大量的系统资源,所以提供这么一...

简单方式
2017/02/04
0
0
Java爬虫抓取知乎20万用户信息并做简易分析

前段时间看@路人甲 分享了一篇爬取知乎用户的文章,心血来潮,想着也该把自己很早写的知乎爬虫完善一下 趁着每天实习回来还有点时间,整理了下思路和原来的代码 因为自己不太爱用框架,所以爬...

KKys
2017/01/21
0
0
XXL-CRAWLER v1.2.1 发布,分布式爬虫框架

版本新特性 JS渲染:支持JS渲染方式采集数据,可参考 "爬虫示例6"; 抽象并设计PageLoader,方便自定义和扩展页面加载逻辑,如JS渲染等。底层提供 "JsoupPageLoader(默认/推荐)","HtmlUnit...

许雪里
2018/02/08
1K
18
virjar/vscrawler

vscrawler vscrawler是一个更加适合抓取的爬虫框架,他不是教科书似的爬虫,准确说他不是爬虫,没有广度优先遍历这些说法,他所面临的网站URL不是网络里面的网络拓扑图而是一个个目标明确的抓...

virjar
2017/06/14
0
0
基于 Redis 的代理 ip 池设计

代理 ip 因为配置简单而且廉价,经常用来作为反反爬虫的手段,但是稳定性一直是其诟病。筛选出优质的代理 ip 并不简单,即使付费购买的代理 ip 源,卖家也不敢保证 100% 可用;另外代理 ip 的...

hellen901004
2018/01/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring使用ThreadPoolTaskExecutor自定义线程池及实现异步调用

多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实现异步调用多线程。 一、ThreadPoolTaskExecutor 本文采用 Executors 的工厂...

CREATE_17
今天
5
0
CSS盒子模型

CSS盒子模型 组成: content --> padding --> border --> margin 像现实生活中的快递: 物品 --> 填充物 --> 包装盒 --> 盒子与盒子之间的间距 content :width、height组成的 内容区域 padd......

studywin
今天
7
0
修复Win10下开始菜单、设置等系统软件无法打开的问题

因为各种各样的原因导致系统文件丢失、损坏、被修改,而造成win10的开始菜单、设置等系统软件无法打开的情况,可以尝试如下方法解决 此方法只在部分情况下有效,但值得一试 用Windows键+R打开...

locbytes
昨天
8
0
jquery 添加和删除节点

本文转载于:专业的前端网站➺jquery 添加和删除节点 // 增加一个三和一节点function addPanel() { // var newPanel = $('.my-panel').clone(true) var newPanel = $(".triple-panel-con......

前端老手
昨天
8
0
一、Django基础

一、web框架分类和wsgiref模块使用介绍 web框架的本质 socket服务端 与 浏览器的通信 socket服务端功能划分: 负责与浏览器收发消息(socket通信) --> wsgiref/uWsgi/gunicorn... 根据用户访问...

ZeroBit
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部