美文网首页
循序渐进学爬虫:多线程+队列爬取豆瓣高分计算机类书籍

循序渐进学爬虫:多线程+队列爬取豆瓣高分计算机类书籍

作者: _Amauri | 来源:发表于2018-10-13 09:19 被阅读35次

    上一次的抓取豆瓣高分计算机书籍的案例,采用的是完全同步的方式。即单个线程依次执行完所有的逻辑,这样存在的问题就是我们的爬虫程序会非常的慢。

    所以本文作为上一次案例的升级版本,通过循序渐进、动手实践的方式来达到更好的学习效果。

    相对于上次的案例,本次主要采用多线程+队列的方式来实现。
    用到的包:
    import requests
    from bs4 import BeautifulSoup
    import re
    import numpy as np
    import csv
    import time
    import threading
    import queue
    

    本次新增了两个包,threading 和 queue。threading 是用来进行多线程编程的,queue 也就是用来创建队列。至于更详细的使用方法,可以上网自行学习。这里就不多做介绍了。

    主要流程:
    1. 生成 URL
    2. 创建两个队列,一个用保存生成的URL(队列1),一个保存HTML文档(队列2)
    3. 创建若干个线程来下载 HTML,并且保存到队列2
    4. 创建若干个线程解析文档
    5. 排序并保存

    代码:

    生成分页URL地址

    def make_url(page)

    根据评分排序

    def _sort(result)

    保存到csv

    def save(data)

    请求url,下载html

    def req_page()

    解析html,获取评分

    def get_content()

    以上前三个方法都没有改动,主要是第四个和第五个。

    req_page(): 用来请求url。

    def req_page():
        while True:
            try:
                url = url_task.get(block=False)
                resp = requests.get(url)
                html = resp.text
                task_html.put(html)
                time.sleep(1)
            except:
                break
    

    以上代码会被若干个线程执行,每一个线程的流程都是不段的从 url_task 也就是我们创建的队列1中取出一个URL,然后执行请求,并把下载到的 HTML 放入队列2。这里有两点要注意的。第一个点就是通过 url_task.get() 方法从队列里拿出任务的时候,由于我们的队列1是提前设定好的,也就是说当下载线程取任务的时候并不会发生 queue.Empty 的异常。只有当队列中的数据被处理完的时候才会执行 except,那么线程就可以通过这个来退出。第二点是sleep这块 ,因为请求太频繁会被豆瓣封掉IP。

    get_content():

    def get_content():
        if task_html.qsize() > 10:
            while True:
                try:
                    html = task_html.get(block=False)
                    bs4 = BeautifulSoup(html, "lxml")
                    book_info_list = bs4.find_all('li', class_='subject-item')
                    if book_info_list is not None:
                        for book_info in book_info_list:
                            list_ = []
                            try:
                                star = book_info.find('span', class_='rating_nums').get_text()
                                if float(star) < 9.0:
                                    continue
                                title = book_info.find('h2').get_text().replace(' ', '').replace('\n', '')
                                comment = book_info.find('span', class_='pl').get_text()
                                comment = re.sub("\D", "", comment)
                                list_.append(title)
                                list_.append(comment)
                                list_.append(star)
                                task_res.append(list_)
                            except:
                                continue
                except:
                    break
    

    这个函数首先判断一下 HTML 文档队列(队列2)的大小是不是大于10,目的是防止解析线程比下载线程执行的快,如果解析线程快于下载线程,那么再还没有下载完所有的URL时,就触发队列的 queue.Empty异常,从而过早退出线程。中间的代码也是上次案例中的代码,不同之处也就是以前是从列表中读取,现在是从队列中读取。同时这个函数也是由多个解析线程执行。

    主函数:

    # 生成分页url
    url_list = make_url(50)
    # url 队列 (队列1)
    url_task = queue.Queue()
    for url in url_list:
        url_task.put(url)
    # 下载好的html队列 (队列2)
    task_html = queue.Queue()
    # 最终结果列表
    task_res = []
    threads = []
    # 获取html线程
    for i in range(5):
        threads.append(threading.Thread(target=req_page))
    # 解析html线程
    threads.append(threading.Thread(target=get_content))
    threads.append(threading.Thread(target=get_content))
    for i in threads:
        i.start()
        i.join()
    # 主线程排序保存
    save(_sort(task_res))
    

    主函数的流程也就是最开始写的五个流程。因为我们创建的所有线程都调用了 join() 方法,那么在最后执行排序和保存操作的时候,所有的子线程都已经执行完毕了。

    相关文章

      网友评论

          本文标题:循序渐进学爬虫:多线程+队列爬取豆瓣高分计算机类书籍

          本文链接:https://www.haomeiwen.com/subject/jnanaftx.html