按部就班学爬虫:多线程+队列爬取豆瓣高分计算机类书籍

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

因此本文做为上一次案例的升级版本,经过按部就班、动手实践的方式来达到更好的学习效果。编程

相对于上次的案例,本次主要采用多线程+队列的方式来实现。
用到的包:
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. 排序并保存

代码:

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

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():url

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异常,从而过早退出线程。中间的代码也是上次案例中的代码,不一样之处也就是之前是从列表中读取,如今是从队列中读取。同时这个函数也是由多个解析线程执行。spa

主函数:线程

# 生成分页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() 方法,那么在最后执行排序和保存操做的时候,全部的子线程都已经执行完毕了。3d

相关文章
相关标签/搜索