线程池实现爬虫

偶然间学习实验楼课程, 看到的, 想到自己刚刚出来找工作的时候, 面试过一家公司让自己实现线程池, 并利用实现的线程池进行多线程爬取, 当时水平太菜(现在也很菜, 不过比那个时候好多了). 这里记录下, 当时没有理解的是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方法实现阻塞等待, 当所有任务都已经执行完成后(任务队列为空, 并且所有现成都处于阻塞状态), 输出统计信息.

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

转载于:https://my.oschina.net/alazyer/blog/743325

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值