在爬取大量数据时,由于有成千上万的数据,单线程爬虫显然不能满足我们的需求,这时候多线程爬虫就来了,本篇文章使用Threading和Queue简单介绍。
私信小编01即可获取大量Python学习资料
首先先了解多线程队列,生产消费模式的大致步骤。1.主线程生成目标链接。2.主线程开启子线程访问队列并爬取数据保存。3.待队列目标为空时关闭线程。
示例代码
主要字段:
city={ 河北省:[石家庄,保定市,秦皇岛,唐山市,邯郸市,邢台市,沧州市,承德市,廊坊市,衡水市,张家口], 山西省:[太原市,大同市,阳泉市,长治市,临汾市,晋中市,运城市,晋城市,忻州市,朔州市,吕梁市], 内蒙古:[呼和浩特,呼伦贝尔,包头市,赤峰市,乌海市,通辽市,鄂尔多斯,乌兰察布,巴彦淖尔], 辽宁省:[盘锦市,鞍山市,抚顺市,本溪市,铁岭市,锦州市,丹东市,辽阳市,葫芦岛,阜新市,朝阳市,营口市], 吉林省:[吉林市,通化市,白城市,四平市,辽源市,松原市,白山市], 黑龙江省:[伊春市,牡丹江,大庆市,鸡西市,鹤岗市,绥化市,双鸭山,七台河,佳木斯,黑河市,齐齐哈尔市], 江苏省:[无锡市,常州市,扬州市,徐州市,苏州市,连云港,盐城市,淮安市,宿迁市,镇江市,南通市,泰州市], 浙江省:[绍兴市,温州市,湖州市,嘉兴市,台州市,金华市,舟山市,衢州市,丽水市], 安徽省:[合肥市,芜湖市,亳州市,马鞍山,池州市,淮南市,淮北市,蚌埠市,巢湖市,安庆市,宿州市,宣城市,滁州市,黄山市,六安市,阜阳市,铜陵市], 福建省:[福州市,泉州市,漳州市,南平市,三明市,龙岩市,莆田市,宁德市], 江西省:[南昌市,赣州市,景德镇,九江市,萍乡市,新余市,抚州市,宜春市,上饶市,鹰潭市,吉安市], 山东省:[潍坊市,淄博市,威海市,枣庄市,泰安市,临沂市,东营市,济宁市,烟台市,菏泽市,日照市,德州市,聊城市,滨州市,莱芜市], 河南省:[郑州市,洛阳市,焦作市,商丘市,信阳市,新乡市,安阳市,开封市,漯河市,南阳市,鹤壁市,平顶山,濮阳市,许昌市,周口市,三门峡,驻马店], 湖北省:[荆门市,咸宁市,襄樊市,荆州市,黄石市,宜昌市,随州市,鄂州市,孝感市,黄冈市,十堰市], 湖南省:[长沙市,郴州市,娄底市,衡阳市,株洲市,湘潭市,岳阳市,常德市,邵阳市,益阳市,永州市,张家界,怀化市], 广东省:[江门市,佛山市,汕头市,湛江市,韶关市,中山市,珠海市,茂名市,肇庆市,阳江市,惠州市,潮州市,揭阳市,清远市,河源市,东莞市,汕尾市,云浮市], 广西省:[南宁市,贺州市,柳州市,桂林市,梧州市,北海市,玉林市,钦州市,百色市,防城港,贵港市,河池市,崇左市,来宾市], 海南省:[海口市,三亚市], 四川省:[乐山市,雅安市,广安市,南充市,自贡市,泸州市,内江市,宜宾市,广元市,达州市,资阳市,绵阳市,眉山市,巴中市,攀枝花,遂宁市,德阳市], 贵州省:[贵阳市,安顺市,遵义市,六盘水], 云南省:[昆明市,玉溪市,大理市,曲靖市,昭通市,保山市,丽江市,临沧市], 西藏:[拉萨市,阿里], 陕西省:[咸阳市,榆林市,宝鸡市,铜川市,渭南市,汉中市,安康市,商洛市,延安市], 甘肃省:[兰州市,白银市,武威市,金昌市,平凉市,张掖市,嘉峪关,酒泉市,庆阳市,定西市,陇南市,天水市], 青海省:[西宁市], 银川省:[银川市,固原市,青铜峡市,石嘴山市,中卫市] } years=[2011,2012,2013,2014,2015,2016,2017,2018,2019,2020] month=[01,02,03,04,05,06,07,08,09,10,11,12] month_less=[01,02,03,04,05,06,07,08,09,10] title=[日期,最高气温,最低气温,天气,风向]导入所需要的包:
from lxml import etree import csv import time import bs4 import random import requests from xpinyin import Pinyin import os import threading from queue import Queue创建队列
q=Queue()因为爬取的是全国省市2011年至今的天气数据,所以本段代码创建了省会路径并想队列传递目标链接。
for province in city.keys(): path=./Thread_Test/{}.format(province) if os.path.exists(path): pass else: os.mkdir(path) for nano_city in city[province]: path=./Thread_Test/{}/{}.format(province,nano_city) if os.path.exists(path): pass else: os.mkdir(path) p=Pinyin() if 市 in nano_city: str_city=nano_city.split(市)[0] str_city=p.get_pinyin(u{}.format(str_city),) print(str_city) else: str_city=p.get_pinyin(u{}.format(nano_city),) for y in years: path=./Thread_Test/{}/{}/{}.format(province,nano_city,y) if os.path.exists(path): pass else: os.mkdir(path) if y==2020: for m in month_less: url={}/{}.html.format(str_city,y+m) info=[province,nano_city,y,m] q.put([url,info]) else: for m in month: url={}/{}.html.format(str_city,y+m) info=[province,nano_city,y,m] q.put([url,info]) print(q.qsize())队列中共有31388条链接
创建线程任务方法:
def working(): while True: #需要使用while 否则线程执行完一次操作就关闭了 url = q.get() #默认队列为空时,线程暂停 doing(url) q.task_done()#告诉队列本次取操作已经完毕def doing(url):#线程在获取到链接后的行为 rsp=requests.get(url=url[0],headers=headers) html_4=bs4.BeautifulSoup(rsp.text,html.parser) ul=html_4.find(ul,class_=thrui) f=open(./Thread_Test/{}/{}/{}/{}.csv.format(url[1][0],url[1][1],url[1][2],url[1][3]),w,encoding=utf-8,newline=) csv_writer=csv.writer(f) csv_writer.writerow(title) for i in ul.find_all(li): lis=[] a=i.find_all(div) for j in a: if len(j.text.split())==0: pass else: lis.append(j.text.split()[0]) print(url[1][0],url[1][1],url[1][2],url[1][3],剩余,q.qsize()) csv_writer.writerow(lis) f.close()创建子线程
threads = [] for i in range(10): #开启十个子线程 t = threading.Thread(target=working) #线程的目标任务为working方法 threads.append(t)开启子线程
for item in threads: item.setDaemon(True) item.start() q.join()#在队列为空时才进行后面的语句,需要配合task_done()使用基本的多线程爬虫就完成了。
还有一个更优的方法生成一个新的队列来储存主线程创建的十个子线程获取到网页数据,子线程并不直接写入,而是交给主线程,主线程再生成新的子线程来写入队列里的数据,这样就减少了十个子线程等待写入文件的时间,专注于爬取数据。
由于数据量太大,测试仅选择了一个省的数据进行爬取。约1300条数据
此图为线程直接写入所需时间
此图为使用新线程专门处理写入数据所需时间
次图为单线程爬虫所需时间
可以看到使用新线程专门处理写入数据时,速度比边爬边写快了30秒=百分之二十,而单线程花费了恐怖的1405秒,为多线程的十倍之多,可见多线程在爬取大量数据时是非常有用的。当然,这么快的速度肯定会遭到反扒处理,封ip之类的,所以要事先准备一个ip池,每个线程在爬取一段时间后就更换一个ip,线程不建议开太多,避免给目标网站服务器造成太大压力,做一个绅士爬虫!
这段代码还可以再继续优化:1.爬虫的类库提取2.线程的类库提取3.存数据库的类库提取4.main()函数优化
初上手多线程,对于锁什么的还不是很懂,若有错误的地方欢迎指明。