Python多线程爬虫—批量爬取豆瓣电影动态加载的电影信息(小白详细说明自己对于多线程了解)

单线程与多线程爬取时间比较

最近听取了老师的建议,开始对多线程爬虫进行自学,在进行多线程爬虫实战之前我做了三点准备,并将准备时所学的东西已写成博文与大家分享,兄你们要是感兴趣的话可以看一看喔😁要是有什么错误的地方可以直接评论私信我

Python—多线程编程(一)线程的创建,管理,停止
Python—多线程编程(二)线程安全(临界资源问题和多线程同步)
Python—Queue模块基本使用方法详解

本博文是使用多线程爬虫爬取豆瓣电影信息的实战解析

1.为什么要学习使用多线程进行爬虫

我之前所写的博文和所进行的爬虫都是单线程爬虫,代码都是在MainThread下运行的,也就是Python的主线程。但当爬取的数据量到一定数量级别的时候,单线程爬虫会耗时很久,达不到大数据对数据要求的及时性,而解决这一时间问题的解决方法可以使用多线程和多进程等。爬虫主要运行时间消耗是请求网页时的io阻塞,所以开启多线程,让不同请求的等待同时进行,可以大大提高爬虫运行效率。(自己简单的理解说明)

2.本此实战采用的模式——生产者,消费者模式(实战解析中会详细说明)

在这里插入图片描述
(1)某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。
(2)产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
(3)单单抽象出生产者和消费者,还够不上是生产者/消费者模式。
(4)该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。
(5)生产者把数据放入缓冲区,而消费者从缓冲区取出数据。

实战解析

1.分析网页源码,构造请求头(谷歌浏览器)

Python帮你了解你喜欢的人!爬取她的微博内容信息!(Ajax数据爬取)

(1)网页信息(可以看到电影信息是通过点击“加载更多”进行加载的,也就是Ajax动态加载,之前的博文中提到过如何爬取这类信息,爬取分析方法一致)

在这里插入图片描述
(2)电影信息源码位置(照片不是很清晰,但可以看出它是以 json 数据格式返回的数据,在面板 XHR 下,用 Aajx 动态加载的)
在这里插入图片描述
(3)分析构造请求头(可以看到它是有请求参数的,分析可得请求页面加载通过 page_start 这个参数控制,每20个为一页,我自己爬虫可得,电影信息应该有384个左右,本此爬取200个电影信息为例)
在这里插入图片描述
构造的请求头为:HTTP请求过程——Chrome浏览器Network详解

headers = {
    'Accept': '*/*',
    'Host': 'movie.douban.com',
    'Referer': 'https://movie.douban.com/explore',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
    #Ajax请求必有的请求头参数之一
    'X-Requested-With': 'XMLHttpRequest'  
}

3.单线程爬取并得到爬取时间(由于比较简单直接上源码和结果):

import threading
import time
from queue import Queue
from urllib.parse import urlencode

import pymysql
import requests

# '''单线程爬取 200个电影信息 用时3.1670422554016113秒'''
headers = {
    'Accept': '*/*',
    'Host': 'movie.douban.com',
    'Referer': 'https://movie.douban.com/explore',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
    'X-Requested-With': 'XMLHttpRequest'
}

# def save(information):
#        #把数据存入数据库
#        #建立数据库连接
#        connection = pymysql.connect(host = 'localhost',
#                                     user = 'root',
#                                     password = '123456',
#                                     database = '豆瓣电影信息',
#                                     charset = 'utf8'
#        )
#        #增加数据
#        for item in information:
#            try:
#                with connection.cursor() as cursor:
#                    sql = 'insert into 电影信息 (电影名字,电影评分,电影链接) values (%s,%s,%s)'
#                    cursor.execute(sql,(item.get('title'),item.get('rate'),item.get('url')))
#                    #提交数据库事务
#                    connection.commit()
#            except pymysql.DatabaseError:
#                #数据库事务回滚
#                connection.rollback()
#
#        connection.close()

sum = 0
def output(json_text):
    global sum
    for item in json_text:
        sum+=1
        print('电影评分:{0},电影名称:{1},电影链接:{2}'.format(item.get('rate'),item.get('title'),item.get('url')))
def get_json_text(url_list):
    for url in url_list:
        response = requests.get(url, headers=headers)
        json_text = response.json()
        output(json_text.get('subjects'))

def main():
    time1 = time.time()
    page = [i * 20 for i in range(10)]
    url_list = []
    for i in page:
        params = {
            'type': 'movie',
            'tag': '热门',
            'sort': 'recommend',
            'page_limit': '20',
            'page_start': i
        }
        url = 'https://movie.douban.com/j/search_subjects?' + urlencode(params)
        url_list.append(url)
    get_json_text(url_list)
    time2 = time.time()
    print('共爬取电影信息:{0}个,共用时:{1}'.format(sum,(time2 - time1)))

if __name__ == '__main__':
    main()

结果:单线程爬取 200个电影信息 用时3.1670422554016113秒

4.多线程进行爬取 (直接上源码代码中有很详细的注释)(5个生产者3个消费者):

import threading
import time
from queue import Queue
from urllib.parse import urlencode

import pymysql
import requests



headers = {
    'Accept': '*/*',
    'Host': 'movie.douban.com',
    'Referer': 'https://movie.douban.com/explore',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
    'X-Requested-With': 'XMLHttpRequest'
}

# 中间者,存放爬取返回结果队列
result_queue = Queue()


class Threading_product(threading.Thread):
	#初始化
    def __init__(self,name,url_queue):
        threading.Thread.__init__(self)
        self.url_queue = url_queue
        self.name = name
    #重写threading中的run()执行方法
    def run(self):
        print('生产者{0}正在爬取...'.format(self.name))
        self.spider()
        print('生产者{0}爬取完成...'.format(self.name))
    #简单的对信息进行爬取
    def spider(self):
        while True:
            if self.url_queue.empty():
                break
            else:
                url = self.url_queue.get()
                response = requests.get(url, headers=headers)
                json_text = response.json()
                # 将返回结果存入中间者,返回结果队列当中
                result_queue.put(json_text.get('subjects'))


class Threading_constumer(threading.Thread):
    #初始化
    def __init__(self,name,result_queue):
        threading.Thread.__init__(self)
        self.name = name
        self.result_queue = result_queue
    #重写run()方法
    def run(self):
        print('消费者{0}正在解析页面并进行电影信息输出:'.format(self.name))
        self.spider()
	#简单的进行输出
    def spider(self):
        while True:
            if self.result_queue.empty():
                break
            else:
                response = self.result_queue.get()
                for item in response:
                    print('电影评分:{0},电影名称:{1},电影链接:{2}'.format(item.get('rate'), item.get('title'), item.get('url')))


def main():
	#产生程序开始时间
    time3 = time.time()
    #每20个为一页的信息,创建10页
    page = [i * 20 for i in range(10)]
    #存放页面的url队列
    url_queue = Queue()
    #构造url链接
    for i in page:
        params = {
            'type': 'movie',
            'tag': '热门',
            'sort': 'recommend',
            'page_limit': '20',
            'page_start': i
        }
        url = 'https://movie.douban.com/j/search_subjects?' + urlencode(params)
        #放入url队列当中
        url_queue.put(url)
    #生产者队列
    threading_product_list = []
    #创建5个生产者
    for i in range(5):
    	#产生生产者
        threading_product = Threading_product(i,url_queue)
        #执行
        threading_product.start()
        #将生产者放入生产者队列当中
        threading_product_list.append(threading_product)
    #遍历生产者,阻塞主线程,也就是说让每一个生成者都执行完自己的线程内容
    for i in threading_product_list:
        i.join()
    #消费者队列
    threading_constumer_list = []
    #创建3个消费者
    for i in range(5):
        #产生消费者
        threading_constumer = Threading_constumer(i,result_queue)
        #执行
        threading_constumer.start()
        #将消费者放入队列当中
        threading_constumer_list.append(threading_constumer)
    #遍历消费者,阻塞主线程,也就是说让每一个消费者都执行完自己的线程内容
    for i in threading_constumer_list:
        i.join()
    #获取代码执行完的最后时间
    time4 = time.time()
    print('共用时:{0}'.format(time4 - time3))

if __name__ == '__main__':
    main()

最终的执行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最后可以清楚的看到
使用多线程爬取200个电影信息的时间是 0.661125898361206秒
而使用单线程的时间是 用时3.1670422554016113秒
差别其实很大,这才200个电影信息,当数据量再大的时候,多线程的速度优势会更快
会持续对多线程进行学习,及时分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章