告诉python,我想“狂飙”了——线程池与异步协程为爬虫提速
本文作者:罗天尧,新疆大学商学院
本文编辑:胡艺粼
技术总编:方一卓
Stata and Python 数据分析
爬虫俱乐部Stata基础课程、Stata进阶课程和Python课程可在小鹅通平台查看,欢迎大家多多支持订阅!如需了解详情,可以通过课程链接(https://appbqiqpzi66527.h5.xiaoeknow.com/homepage/10)或课程二维码进行访问哦~许多资料整理者,深受网速的困扰。但或许这种困扰并不能完全归咎于网速,如果能充分调动计算机的“主观能动性”,在请求端我们将能够更高效地搜集数据。
工欲善其事,必先利其器。为了掌握高效率的爬虫技术,我们首先要建立一个学习工具——爬虫程序。进入水果交易网站(https://www.guo68.com/),将页面切换到水果行情中。
import requests
cookies = {
'PHPSESSID': 'juntbj9ejl8dnroa8qa4r7pbnu',
}
headers = {
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Referer': 'https://www.guo68.com/market?',
'Accept-Language': 'zh-CN,zh;q=0.9',
# 'Cookie': 'PHPSESSID=juntbj9ejl8dnroa8qa4r7pbnu',
}
resp = requests.get('https://www.guo68.com/market', cookies=cookies, headers=headers).text
print(resp)
可以看到,返回的数据中包含了数据信息。from lxml import etree
html = etree.HTML(resp) #建立解析对象
table = html.xpath('/html/body/div[3]/div/div[1]/div[3]/div[2]/ul/a/li/text()') #注意这里需要手动微调,删去多余的索引值
print(table)
可以看到,数据信息已经被提取出来。def spider(url):
resp = requests.get(url, cookies=cookies, headers=headers).text
html = etree.HTML(resp)
table = html.xpath('/html/body/div[3]/div/div[1]/div[3]/div[2]/ul/a')
for tr in table : #table仍保留了xpath属性,我们可以借此生成工整的信息格式
txt = tr.xpath('./li/text()') #在此将每个<a>标签中的文字信息提取
csv.writerow(list(txt)) #将一组<a>标签中的信息以列表形式传入
if __name__ == '__main__':
f = open('data.csv', encoding='utf-8', mode='w', newline='') #newline去除空行
csv = csv.writer(f) #提前打开为对象,避免反复打开文件耗时
start=time.time() #可以提前import time进行测试的计时操作
for i in range(1, 200):
url=f'https://www.guo68.com/market?page={i}'
spider(url)
end=time.time()
print(f"耗时:{end-start}s")
执行代码并等待信息录入。D:\Anaconda\python.exe "D:/pycharm project/test.py"
耗时:47.112051486968994s
Process finished with exit code 0
查看已经写入的csv文件。from concurrent.futures import ThreadPoolExecutor #导包
def spider(url):
resp = requests.get(url, cookies=cookies, headers=headers).text
html = etree.HTML(resp)
table = html.xpath('/html/body/div[3]/div/div[1]/div[3]/div[2]/ul/a')
for tr in table :
txt = tr.xpath('./li/text()')
csv.writerow(list(txt))
if __name__ == '__main__':
f = open('data.csv', encoding='utf-8', mode='w', newline='')
csv = csv.writer(f)
start=time.time()
with ThreadPoolExecutor(50) as t: #开辟线程
for i in range(1, 200):
t.submit(spider, f'https://www.guo68.com/market?page={i}') #开始线程
end=time.time()
print(f"耗时:{end-start}s")
t.shutdown(wait= True) #关闭线程
执行上述代码,我们的爬虫速度已经有了明显提升。D:\Anaconda\python.exe "D:/pycharm project/test.py"
耗时:13.467449502944946s
Process finished with exit code 0
当然,线程池在数据传输上也会有所缺陷。关于此问题,或许你还要了解线程安全与互斥锁等。感兴趣的读者可以测试以下代码,此处不过多赘述。#输出结果有随机数据污染(多刷新几次)
from threading import Thread
def task(count: int):
for n in range(count):
print(n)
thread1 = Thread(target=task, args=(10,))
thread2 = Thread(target=task, args=(20,))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("Main threads is end")
3.2 异步协程简单来看,单线程的爬虫就像一场接力赛,需要在爬取和存储直接切换。但显然,请求和写入是两个独立的动作,他们只是在“无为地等待生命中的下一次”。
import asyncio
import aiohttp
f = open('data.csv', encoding='utf-8', mode='w', newline='')
c = csv.writer(f)
url_list = [ f"https://www.guo68.com/market?page={x}" for x in range(1, 200)]
async def spider(url):
async with aiohttp.ClientSession() as session: # 发送网络请求
async with session.get(url,cookies=cookies, headers=headers) as resp:
resp = await resp.content.read()
html = etree.HTML(resp)
table = html.xpath('/html/body/div[3]/div/div[1]/div[3]/div[2]/ul/a')
for tr in table:
txt = tr.xpath('./li/text()')
c.writerow(list(txt))
async def main():
tasks = []
for url in url_list:
tasks.append(spider(url))
await asyncio.wait(tasks)
if __name__ == '__main__':
start=time.time()
# asyncio.run(main()) #RuntimeError: Event loop is closed
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
end=time.time()
print(f"耗时:{end-start}s")
D:\Anaconda\python.exe "D:/pycharm project/test.py"
耗时:11.6561918258667s
Process finished with exit code 0
观察爬取到的文件,由于异步的存在,我们爬取的数据顺序并不是固定的。读者也可以继续通过生产消费者模型,来理解这一现象。重磅福利!为了更好地服务各位同学的研究,爬虫俱乐部将在小鹅通平台上持续提供金融研究所需要的各类指标,包括上市公司十大股东、股价崩盘、投资效率、融资约束、企业避税、分析师跟踪、净资产收益率、资产回报率、国际四大审计、托宾Q值、第一大股东持股比例、账面市值比、沪深A股上市公司研究常用控制变量等一系列深加工数据,基于各交易所信息披露的数据利用Stata在实现数据实时更新的同时还将不断上线更多的数据指标。我们以最前沿的数据处理技术、最好的服务质量、最大的诚意望能助力大家的研究工作!相关数据链接,请大家访问:(https://appbqiqpzi66527.h5.xiaoeknow.com/homepage/10)或扫描二维码:
最后,我们为大家揭秘雪球网(https://xueqiu.com/)最新所展示的沪深证券和港股关注人数增长Top10。
对我们的推文累计打赏超过1000元,我们即可给您开具发票,发票类别为“咨询费”。用心做事,不负您的支持!
往期推文推荐 高级函数——map()和reduce()
Stata绘制条形图的进阶用法
快来看看武汉的房价是不是又双叒叕涨了!Python 常见内置函数(二)Stata绘制饼形图的进阶用法
Python标准库--logging模块盲区探索——Stata的读写极限Camelot提取PDF表格:一页多表、多页一表Stata绘图系列——条形图绘制
Python常见内置函数(一)Stata绘图系列——饼形图绘制【爬虫实战】深交所服务业年报数据“挂羊头卖狗肉”?
Python与excel交互--xlsxwriter模块cnmapsearch——离公司最近的快餐店在哪
Python中的异常处理 Python交互式数据可视化——酷炫的Altair库 hk系列命令(3)—— hktrade关于我们
微信公众号“Stata and Python数据分析”分享实用的Stata、Python等软件的数据处理知识,欢迎转载、打赏。我们是由李春涛教授领导下的研究生及本科生组成的大数据处理和分析团队。
武汉字符串数据科技有限公司一直为广大用户提供数据采集和分析的服务工作,如果您有这方面的需求,请发邮件到statatraining@163.com,或者直接联系我们的数据中台总工程司海涛先生,电话:18203668525,wechat: super4ht。海涛先生曾长期在香港大学从事研究工作,现为知名985大学的博士生,爬虫俱乐部网络爬虫技术和正则表达式的课程负责人。
此外,欢迎大家踊跃投稿,介绍一些关于Stata和Python的数据处理和分析技巧。
投稿邮箱:statatraining@163.com投稿要求:1)必须原创,禁止抄袭;2)必须准确,详细,有例子,有截图;注意事项:1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。2)邮件请注明投稿,邮件名称为“投稿+推文名称”。3)应广大读者要求,现开通有偿问答服务,如果大家遇到有关数据处理、分析等问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。