单线程、多线程和协程的爬虫性能对比
作者:小小明
非常擅长解决各类复杂数据处理的逻辑,各类结构化与非结构化数据互转,字符串解析匹配等等。
至今已经帮助很多数据从业者解决工作中的实际问题,如果你在数据处理上遇到什么困难,欢迎与我交流。
豆瓣深圳影讯爬虫
文章目录
- 豆瓣深圳影讯爬虫
- 爬取测试
- 单线程爬虫
- 多线程爬虫
- 协程异步爬虫
- 回顾
- 彩蛋:xpath+pandas解析表格并提取url
- 结语
大家好,我是小小明。今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程、多线程和协程来爬取,从而对比单线程、多线程和协程在网络爬虫中的性能。
具体要爬的网址是:https://movie.douban.com/cinema/later/shenzhen/
除了要爬入口页以外还需爬取每个电影的详情页,具体要爬取的结构信息如下:
爬取测试
下面我演示使用xpath解析数据。
入口页数据读取:
import requests
from lxml import etree
import pandas as pd
import remain_url = "https://movie.douban.com/cinema/later/shenzhen/"
headers = {"Accept-Encoding": "Gzip","User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
}
r = requests.get(main_url, headers=headers)
r
结果:
<Response [200]>
检查一下所需数据的xpath:
可以看到每个电影信息都位于id为showing-soon
下面的div里面,再分别分析内部的电影名称、url和想看人数所处的位置,于是可以写出如下代码:
html = etree.HTML(r.text)
all_movies = html.xpath("//div[@id='showing-soon']/div")
result = []
for e in all_movies:# imgurl, = e.xpath(".//img/@src")name, = e.xpath(".//div[@class='intro']/h3/a/text()")url, = e.xpath(".//div[@class='intro']/h3/a/@href")# date, movie_type, pos = e.xpath(".//div[@class='intro']/ul/li[@class='dt']/text()")like_num, = e.xpath(".//div[@class='intro']/ul/li[@class='dt last']/span/text()")result.append((name, int(like_num[:like_num.find("人")]), url))
main_df = pd.DataFrame(result, columns=["影名", "想看人数", "url"])
main_df
结果:
然后再选择一个详情页的url进行测试,我选择了熊出没·狂野大陆这部电影,因为文本数据相对最复杂,也最具备代表性:
url = main_df.at[17, "url"]
url
结果:
'https://movie.douban.com/subject/34825886/'
分析详情页结构:
文本信息都在这个位置中,下面我们直接提取这个div下面的所有文本节点:
r = requests.get(url, headers=headers)
html = etree.HTML(r.text)
movie_infos = html.xpath("//div[@id='info']//text()")
print(movie_infos)
结果:
['\n ', '导演', ': ', '丁亮', '\n ', '编剧', ': ', '徐芸', ' / ', '崔铁志', ' / ', '张宇', '\n ', '主演', ': ', '张伟', ' / ', '张秉君', ' / ', '谭笑', '\n ', '类型:', ' ', '喜剧', ' / ', '科幻', ' / ', '动画', '\n \n ', '制片国家/地区:', ' 中国大陆', '\n ', '语言:', ' 汉语普通话', '\n ', '上映日期:', ' ', '2021-02-12(中国大陆)', ' / ', '2020-08-01(上海电影节)', '\n ', '片长:', ' ', '100分钟', '\n ', '又名:', ' 熊出没大电影7 / 熊出没科幻大电影 / Boonie Bears: The Wild Life', '\n ', 'IMDb链接:', ' ', 'tt11654032', '\n\n']
为了阅读方便,拼接一下:
movie_info_txt = "".join(movie_infos)
print(movie_info_txt)
结果:
导演: 丁亮编剧: 徐芸 / 崔铁志 / 张宇主演: 张伟 / 张秉君 / 谭笑类型: 喜剧 / 科幻 / 动画制片国家/地区: 中国大陆语言: 汉语普通话上映日期: 2021-02-12(中国大陆) / 2020-08-01(上海电影节)片长: 100分钟又名: 熊出没大电影7 / 熊出没科幻大电影 / Boonie Bears: The Wild LifeIMDb链接: tt11654032
接下来就简单了:
row = {}
for line in re.split("[\n ]*\n[\n ]*", movie_info_txt):line = line.strip()arr = line.split(": ", maxsplit=1)if len(arr) != 2:continuek, v = arrrow[k] = v
row
结果:
{'导演': '丁亮','编剧': '徐芸 / 崔铁志 / 张宇','主演': '张伟 / 张秉君 / 谭笑','类型': '喜剧 / 科幻 / 动画','制片国家/地区': '中国大陆','语言': '汉语普通话','上映日期': '2021-02-12(中国大陆) / 2020-08-01(上海电影节)','片长': '100分钟','又名': '熊出没大电影7 / 熊出没科幻大电影 / Boonie Bears: The Wild Life','IMDb链接': 'tt11654032'}
可以看到成功的切割出了每一项。
下面根据上面的测试基础,我们完善整体的爬虫代码:
单线程爬虫
import requests
from lxml import etree
import pandas as pd
import remain_url = "https://movie.douban.com/cinema/later/shenzhen/"
headers = {"Accept-Encoding": "Gzip","User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
}
r = requests.get(main_url, headers=headers)
html = etree.HTML(r.text)
all_movies = html.xpath("//div[@id='showing-soon']/div")
result = []
for e in all_movies:imgurl, = e.xpath(".//img/@src")name, = e.xpath(".//div[@class='intro']/h3/a/text()")url, = e.xpath(".//div[@class='intro']/h3/a/@href")print(url)
# date, movie_type, pos = e.xpath(".//div[@class='intro']/ul/li[@class='dt']/text()")like_num, = e.xpath(".//div[@class='intro']/ul/li[@class='dt last']/span/text()")r = requests.get(url, headers=headers)html = etree.HTML(r.text)row = {}row["电影名称"] = namefor line in re.split("[\n ]*\n[\n ]*", "".join(html.xpath("//div[@id='info']//text()")).strip()):line = line.strip()arr = line.split(": ", maxsplit=1)if len(arr) != 2:continuek, v = arrrow[k] = vrow["想看人数"] = int(like_num[:like_num.find("人")])
# row["url"] = url
# row["图片地址"] = imgurl
# print(row)result.append(row)
df = pd.DataFrame(result)
df.sort_values("想看人数", ascending=False, inplace=True)
df.to_csv("shenzhen_movie.csv", index=False)
结果:
https://movie.douban.com/subject/26752564/
https://movie.douban.com/subject/35172699/
https://movie.douban.com/subject/34992142/
https://movie.douban.com/subject/30349667/
https://movie.douban.com/subject/30283209/
https://movie.douban.com/subject/33457717/
https://movie.douban.com/subject/30487738/
https://movie.douban.com/subject/35068230/
https://movie.douban.com/subject/27039358/
https://movie.douban.com/subject/30205667/
https://movie.douban.com/subject/30476403/
https://movie.douban.com/subject/30154423/
https://movie.douban.com/subject/27619748/
https://movie.douban.com/subject/26826330/
https://movie.douban.com/subject/26935283/
https://movie.douban.com/subject/34841067/
https://movie.douban.com/subject/34880302/
https://movie.douban.com/subject/34825886/
https://movie.douban.com/subject/34779692/
https://movie.douban.com/subject/35154209/
爬到的文件:
整体耗时:
42.5秒。
多线程爬虫
单线程的爬取耗时还是挺长的,下面看看使用多线程的爬取效率:
import requests
from lxml import etree
import pandas as pd
import re
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETEDdef fetch_content(url):print(url)headers = {"Accept-Encoding": "Gzip", # 使用gzip压缩传输数据让访问更快"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"}r = requests.get(url, headers=headers)return r.texturl = "https://movie.douban.com/cinema/later/shenzhen/"
init_page = fetch_content(url)
html = etree.HTML(init_page)
all_movies = html.xpath("//div[@id='showing-soon']/div")
result = []
for e in all_movies:
# imgurl, = e.xpath(".//img/@src")name, = e.xpath(".//div[@class='intro']/h3/a/text()")url, = e.xpath(".//div[@class='intro']/h3/a/@href")
# date, movie_type, pos = e.xpath(".//div[@class='intro']/ul/li[@class='dt']/text()")like_num, = e.xpath(".//div[@class='intro']/ul/li[@class='dt last']/span/text()")result.append((name, int(like_num[:like_num.find("人")]), url))
main_df = pd.DataFrame(result, columns=["影名", "想看人数", "url"])max_workers = main_df.shape[0]
with ThreadPoolExecutor(max_workers=max_workers) as executor:future_tasks = [executor.submit(fetch_content, url) for url in main_df.url]wait(future_tasks, return_when=ALL_COMPLETED)pages = [future.result() for future in future_tasks]result = []
for url, html_text in zip(main_df.url, pages):html = etree.HTML(html_text)row = {}for line in re.split("[\n ]*\n[\n ]*", "".join(html.xpath("//div[@id='info']//text()")).strip()):line = line.strip()arr = line.split(": ", maxsplit=1)if len(arr) != 2:continuek, v = arrrow[k] = vrow["url"] = urlresult.append(row)
detail_df = pd.DataFrame(result)
df = main_df.merge(detail_df, on="url")
df.drop(columns=["url"], inplace=True)
df.sort_values("想看人数", ascending=False, inplace=True)
df.to_csv("shenzhen_movie2.csv", index=False)
df
结果:
耗时8秒。
由于每个子页面都是单独的线程爬取,每个线程几乎都是同时在工作,所以最终耗时仅取决于爬取最慢的子页面。
协程异步爬虫
由于我在jupyter中运行,为了使协程能够直接在jupyter中直接运行,所以我在代码中增加了下面两行代码,在普通编辑器里面可以去掉:
import nest_asyncio
nest_asyncio.apply()
这个问题是因为jupyter所依赖的高版本Tornado存在bug,将Tornado退回到低版本也可以解决这个问题。
下面我使用协程来完成这个需求的爬取:
import aiohttp
from lxml import etree
import pandas as pd
import re
import asyncio
import nest_asyncio
nest_asyncio.apply()async def fetch_content(url):print(url)header = {"Accept-Encoding": "Gzip", # 使用gzip压缩传输数据让访问更快"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"}async with aiohttp.ClientSession(headers=header, connector=aiohttp.TCPConnector(ssl=False)) as session:async with session.get(url) as response:return await response.text()async def main():url = "https://movie.douban.com/cinema/later/shenzhen/"init_page = await fetch_content(url)html = etree.HTML(init_page)all_movies = html.xpath("//div[@id='showing-soon']/div")result = []for e in all_movies:# imgurl, = e.xpath(".//img/@src")name, = e.xpath(".//div[@class='intro']/h3/a/text()")url, = e.xpath(".//div[@class='intro']/h3/a/@href")# date, movie_type, pos = e.xpath(".//div[@class='intro']/ul/li[@class='dt']/text()")like_num, = e.xpath(".//div[@class='intro']/ul/li[@class='dt last']/span/text()")result.append((name, int(like_num[:like_num.find("人")]), url))main_df = pd.DataFrame(result, columns=["影名", "想看人数", "url"])tasks = [fetch_content(url) for url in main_df.url]pages = await asyncio.gather(*tasks)result = []for url, html_text in zip(main_df.url, pages):html = etree.HTML(html_text)row = {}for line in re.split("[\n ]*\n[\n ]*", "".join(html.xpath("//div[@id='info']//text()")).strip()):line = line.strip()arr = line.split(": ", maxsplit=1)if len(arr) != 2:continuek, v = arrrow[k] = vrow["url"] = urlresult.append(row)detail_df = pd.DataFrame(result)df = main_df.merge(detail_df, on="url")df.drop(columns=["url"], inplace=True)df.sort_values("想看人数", ascending=False, inplace=True)return dfdf = asyncio.run(main())
df.to_csv("shenzhen_movie3.csv", index=False)
df
结果:
耗时仅7秒,相对比多线程更快一点。
由于request库不支持协程,所以我使用了支持协程的aiohttp进行页面抓取。当然实际爬取的耗时还取绝于当时的网络,但整体来说,协程爬取会比多线程爬虫稍微快一些。
回顾
今天我向你演示了,单线程爬虫、多线程爬虫和协程爬虫。可以看到一般情况下协程爬虫速度最快,多线程爬虫略慢一点,单线程爬虫则必须上一个页面爬取完成才能继续爬取。
但协程爬虫相对来说并不是那么好编写,数据抓取无法使用request库,只能使用aiohttp。所以在实际编写爬虫时,我们一般都会使用多线程爬虫来提速,但必须注意的是网站都有ip访问频率限制,爬的过快可能会被封ip,所以一般我们在多线程提速的同时使用代理ip来并发的爬取数据。
彩蛋:xpath+pandas解析表格并提取url
我们在深圳影讯的底部能够看到一个> 查看全部即将上映的影片的按钮,点进去能够看到一张完整近期上映电影的列表,发现这个列表是个table标签的数据:
那就简单了,解析table我们可能压根就不需要用xpath,直接用pandas即可,但片名中包含的url地址还需解析,所以我采用xpath+pandas来解析这个网页,看看我的代码吧:
import pandas as pd
import requests
from lxml import etreeheaders = {"Accept-Encoding": "Gzip","User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
}
r = requests.get("https://movie.douban.com/coming", headers=headers)
html = etree.HTML(r.text)
table_tag = html.xpath("//table")[0]
df, = pd.read_html(etree.tostring(table_tag))
urls = table_tag.xpath(".//td[2]/a/@href")
df["url"] = urls
df
结果:
上映日期 | 片名 | 类型 | 制片国家 / 地区 | 想看 | url | |
---|---|---|---|---|---|---|
0 | 01月22日 | 小伟 | 剧情 | 中国大陆 | 18340人 | https://movie.douban.com/subject/26752564/ |
1 | 01月22日 | 武汉日夜 | 纪录片 | 中国大陆 | 16208人 | https://movie.douban.com/subject/35172699/ |
2 | 01月22日 | 幸运电梯 | 剧情 | 中国大陆 | 11504人 | https://movie.douban.com/subject/34992142/ |
3 | 01月22日 | 指挥家 | 剧情 / 传记 / 爱情 | 荷兰 | 8629人 | https://movie.douban.com/subject/30349667/ |
4 | 01月22日 | 与我跳舞 | 喜剧 / 歌舞 | 日本 | 5960人 | https://movie.douban.com/subject/30283209/ |
5 | 01月22日 | 大红包 | 喜剧 / 爱情 | 中国大陆 | 5221人 | https://movie.douban.com/subject/33457717/ |
6 | 01月23日 | 魔法鼠乐园 | 喜剧 / 动画 / 家庭 | 中国大陆 | 244人 | https://movie.douban.com/subject/30487738/ |
7 | 01月29日 | 吉祥如意 | 剧情 / 家庭 | 中国大陆 | 16914人 | https://movie.douban.com/subject/35068230/ |
8 | 01月29日 | 蜂鸟计划 | 剧情 | 加拿大 | 6780人 | https://movie.douban.com/subject/27039358/ |
9 | 01月29日 | 温泉屋的小老板娘 | 动画 / 家庭 | 日本 | 3736人 | https://movie.douban.com/subject/30205667/ |
10 | 01月29日 | 移情高手 | 喜剧 / 爱情 | 中国大陆 | 308人 | https://movie.douban.com/subject/30476403/ |
11 | 01月30日 | 黄河尕谣 | 纪录片 / 音乐 | 中国大陆 | 1742人 | https://movie.douban.com/subject/30154423/ |
12 | 02月12日 | 唐人街探案3 | 喜剧 / 悬疑 | 中国大陆 | 279735人 | https://movie.douban.com/subject/27619748/ |
13 | 02月12日 | 刺杀小说家 | 动作 / 奇幻 / 冒险 | 中国大陆 | 59601人 | https://movie.douban.com/subject/26826330/ |
14 | 02月12日 | 侍神令 | 奇幻 | 中国大陆 | 40954人 | https://movie.douban.com/subject/26935283/ |
15 | 02月12日 | 你好,李焕英 | 剧情 / 喜剧 | 中国大陆 | 37921人 | https://movie.douban.com/subject/34841067/ |
16 | 02月12日 | 人潮汹涌 | 剧情 / 喜剧 / 犯罪 | 中国大陆 | 29401人 | https://movie.douban.com/subject/34880302/ |
17 | 02月12日 | 熊出没·狂野大陆 | 喜剧 / 科幻 / 动画 | 中国大陆 | 9499人 | https://movie.douban.com/subject/34825886/ |
18 | 02月12日 | 新神榜:哪吒重生 | 动作 / 动画 / 奇幻 | 中国大陆 | 8408人 | https://movie.douban.com/subject/34779692/ |
19 | 02月26日 | 小美人鱼的奇幻冒险 | 动画 / 奇幻 / 冒险 | 中国大陆 | 2580人 | https://movie.douban.com/subject/35154209/ |
20 | 03月12日 | 北京爱情图鉴 | 剧情 | 中国大陆 | 1259人 | https://movie.douban.com/subject/27067713/ |
21 | 04月01日 | 来都来了 | 喜剧 | 中国大陆 | 1298人 | https://movie.douban.com/subject/34670706/ |
22 | 04月02日 | 我的姐姐 | 剧情 | 中国大陆 | 6427人 | https://movie.douban.com/subject/35158160/ |
23 | 04月30日 | 古董局中局 | 剧情 / 悬疑 / 冒险 | 中国大陆 | 16121人 | https://movie.douban.com/subject/26996619/ |
24 | 05月01日 | 秘密访客 | 剧情 / 悬疑 | 中国大陆 | 42302人 | https://movie.douban.com/subject/30378158/ |
25 | 05月01日 | 阳光劫匪 | 剧情 / 喜剧 / 奇幻 | 中国大陆 | 5974人 | https://movie.douban.com/subject/26933158/ |
26 | 05月01日 | 西游记之再世妖王 | 喜剧 / 动画 / 奇幻 | 中国大陆 | 3137人 | https://movie.douban.com/subject/26818326/ |
27 | 05月20日 | 你的婚礼 | 爱情 | 中国大陆 | 7148人 | https://movie.douban.com/subject/34973399/ |
28 | 07月01日 | 革命者 | 剧情 / 历史 | 中国大陆 | 740人 | https://movie.douban.com/subject/35288767/ |
29 | 07月01日 | 1921 | 剧情 / 历史 | 中国大陆 | 4人 | https://movie.douban.com/subject/35125443/ |
30 | 2022年02月01日 | 我心飞扬 | 剧情 / 传记 | 中国大陆 | 756人 | https://movie.douban.com/subject/32712599/ |
这样就能到了主页面的完整数据,再简单的处理一下即可。
结语
感谢各位读者,有什么想法和收获欢迎留言评论噢!
单线程、多线程和协程的爬虫性能对比相关推荐
- Python 爬虫:单线程、多线程和协程的爬虫性能对比
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以点击下方链接自行获取 Python免费学习资料 ...
- Python爬虫:单线程、多线程和协程的爬虫性能对比!
大家好,我是漆柒七7! 首先见面礼 Python学习大礼包 点击领取 然后今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程.多线程和协程来爬取,从而对比单线程.多线 ...
- Python爬虫:单线程、多线程和协程的爬虫性能对比
今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程.多线程和协程来爬取,从而对比单线程.多线程和协程在网络爬虫中的性能. 具体要爬的网址是:https://movie ...
- Python爬虫纯干货:单线程、多线程和协程的爬虫性能对比
今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程.多线程和协程来爬取,从而对比单线程.多线程和协程在网络爬虫中的性能. 具体要爬的网址是:https://movie ...
- Python爬虫:单线程、多线程和协程的爬虫性能对比!异步才是永远滴神!
然后今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程.多线程和协程来爬取,从而对比单线程.多线程和协程在网络爬虫中的性能. 具体要爬的网址是:https://mov ...
- 爬虫的单线程+多任务异步协程:asyncio 3.6
单线程+多任务异步协程:asyncio 3.6 事件循环 无限循环的对象.事件循环中最终需要将一些 特殊的函数(被async关键字修饰的函数) 注册在该对象中. 协程 本质上是一个对象.可以把协程对象 ...
- 使用单线程,多线程,协程,异步爬取包图网大国工匠视频
使用单线程爬取,多线程,协程爬取,异步爬取包图网视频 文章目录 使用单线程爬取,多线程,协程爬取,异步爬取包图网视频 第一步使用单线程爬取包图网视频 多线程爬取视频 用协程爬取 基于协程的异步爬取 第 ...
- 爬虫第四章 单线程+多任务异步协程
单线程+多任务异步协程: asyncio 事件循环 loop: 无限循环的对象,事件循环中最终需要将一些特殊的函数注册到该事件循环中特殊的函数: 被ansyc关键字修饰的函数协程: 本质上是一个对象, ...
- Cpython解释器下实现并发编程——多进程、多线程、协程、IO模型
一.背景知识 进程即正在执行的一个过程.进程是对正在运行的程序的一个抽象. 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内容都 ...
最新文章
- 【正则表达式系列】一些概念(字符组、捕获组、非捕获组)
- 使用c++进行Windows编程中各种操作文件的方法 【转】
- App 上传遇到问题
- JVM_07 Class文件结构
- golang基本数据类型转换
- 局域网共享工具_局域网文件共享
- 控制台下修改系统驱动状态的代码
- 21 年总结:文章的盘点和写作背景
- 如何查看selenium的版本号
- 项目实战-使用PySpark处理文本多分类问题
- c语言switch做一个计算器,超级新手,用switch写了个计算器程序,求指导
- 怎么用命令开远程主机的telnet服务1
- 数据结构之线性表代码实现顺序存储,链式存储,静态链表(选自大话数据结构)...
- fir.im Weekly - 2016 年 Android 最佳实践列表 1
- H.266/VVC代码学习:xCheckRDCostAffineMerge2Nx2N函数
- 基于树莓派的视频会议系统
- HTML META 元数据标签详解
- PDF文件secured去除限制
- javaSE探赜索隐之一<小生的第一篇CSDN博客,欢迎大家指正>
- 应用提交 App Store 上架被拒的原因都有哪些