最近听取了老师的建议,开始对多线程爬虫进行自学,在进行多线程爬虫实战之前我做了三点准备,并将准备时所学的东西已写成博文与大家分享,兄你们要是感兴趣的话可以看一看喔要是有什么错误的地方可以直接评论私信我
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个电影信息,当数据量再大的时候,多线程的速度优势会更快
会持续对多线程进行学习,及时分享。