python爬虫高级教程:多线程队列,生产消费模式爬虫

本文介绍了如何使用Python的Threading和Queue库实现多线程爬虫,通过生产者消费者模式提高爬取效率。文章通过实例展示了在爬取大量数据时,多线程如何减少爬取时间,并提到了防止反扒措施和代码优化方向。
摘要由CSDN通过智能技术生成

在爬取大量数据时,由于有成千上万的数据,单线程爬虫显然不能满足我们的需求,这时候多线程爬虫就来了,本篇文章使用Threading和Queue简单介绍。

很多人学习python,不知道从何学起。
很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。
很多已经做案例的人,却不知道如何去学习更加高深的知识。
那么针对这三类人,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍,以及课程的源代码!??¤
QQ群:623406465

首先先了解多线程队列,生产消费模式的大致步骤。
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='https://lishi.tianqi.com/{}/{}.html'.format(str_city,y+m)
                    info=[province,nano_city,y,m]
                    q.put([url,info])
            else:
                for m in month:
                    url='https://lishi.tianqi.com/{}/{}.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()函数优化

初上手多线程,对于锁什么的还不是很懂,若有错误的地方欢迎指明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值