Python爬虫开发加速神器,就问你怕不?

作者:皮克啪的铲屎官

来源:皮爷撸码

大名鼎鼎的aiohttp,相信如果你学习Python或者爬虫的时候,肯定听说过这个东西。没听过也不要紧,今天看完文章,只要记住,aiohttp这个东西,在写爬虫的时候,很牛逼就行了。

aiohttp 就是一个用 asyncio实现的 HTTP client/server。 你可以通过它来简单实现一个具有异步处理功能的 clients 和 servers。 aiohttp同时还支持 Server WebSockets 和 Client WebSockets,让你使用起来更加简单。

1、我们的爬虫需求

皮爷最近在做一个项目,就是用微信小程序追美剧的项目,那么首先,我们得需要有一个所有美剧的来源,恰巧,下面这个排行榜就有我们所有需要的信息:

http://www.ttmeiju.vip/index.php/summary/index/p/1.html

初级要求

我们很简单,就是需要从【第一页】:

http://www.ttmeiju.vip/index.php/summary/index/p/1.html

一直爬到最后一页【第三十五页】:

http://www.ttmeiju.vip/index.php/summary/index/p/35.html

中级要求

由于排行榜页面没有美剧的【季】信息,这个必须进入详情页来做,所以,中级要求就是针对每一条美剧,进入详情页,从里面爬取出来当前美剧的【季】信息。

这个要求不难吧?就是一级页面变换 page number 的数值来爬取信息。就算要爬取【季】信息,我们的爬虫深度也就才两级。

所以,这个需求不难。而且网页都是静态资源,一般简单的爬虫程序就能hou住。

2、撸码前的整理

这一步,我们需要想想通过什么样的方法能够实现上面的需求。

熟悉皮爷的童鞋都知道,皮爷之前的爬虫程序主要用 Scrapy 这个框架。为啥主要用这个?主要这个是一个框架。框架的意思就是写起来简单。何为简单?你只需要专注写爬虫的相关逻辑部分就好,不需要管理程序的生命周期,代码控制之类的问题,因为框架都给你整理好了。

那么,我们的需求就可以用两种做法来搞:

  1. 用 Scrapy 来写。
  2. 自己写爬虫,但是要用到 aiohttp 的东西。

下面皮爷就简单为大家来说一下他们是怎么实现的,以及最后对比结果。

3、Scrapy撸发撸起来

scrapy的写法,皮爷之前写过很多遍了,具体的教学文章,可以参考皮爷之前写的:

基于云服务的网站种子采集器,还能发送到邮箱,你不来考虑一下?

这里,我们就直接开始说具体的实现代码了。代码实现的就是从1页爬取到35页面,先不考虑“两层爬取”的数据。

class TtmjspiderSpider(scrapy.Spider):
 name = '皮爷spider'
 root_url = "http://www.ttmeiju.vip"
 def start_requests(self):
 start_url = "http://www.ttmeiju.vip/index.php/summary/index/p/1.html"
 yield Request(url=start_url, callback=self.parse_page, dont_filter=True, meta={"cur_page": 1, "max_page_num": -1})
 def parse_page(self, response):
 content = response.body
 soup = BeautifulSoup(content, "html.parser")
 cur_page = response.meta["cur_page"]
 cur_url = response.url
 max_page_num = response.meta["max_page_num"]
 # 第一页找top3的标签
 rank_top_3_div = soup.find_all(name="div", attrs={"class": "ranktop3"})
 for item in rank_top_3_div:
 link_a = item.find_all(name="a")[0]
 tv_url = self.root_url + link_a["href"]
 tv_name = link_a.text
 tv_rank_num = item.find_all(name="div", attrs={"class": "ranknum"})[0].text
 play_info_div = item.find_all(name="div", attrs={"class": "mjinfo"})
 play_info_one = play_info_div[0].text
 play_info_two = play_info_div[1].text
 tv_category = play_info_one.split("/")[0].strip()
 tv_status = play_info_one.split("/")[1].strip()
 tv_update_day = play_info_one.split("/")[2].split(":")[-1].strip()
 temp_result = re.findall("\d{4}-\d{2}-\d{2}", play_info_two)
 if len(temp_result) != 0:
 tv_return_date = temp_result[0]
 else:
 tv_return_date = "暂无"
 # 构建 item
 tv_item = TtmjTvPlayItem()
 tv_item["tv_play_name"] = tv_name
 tv_item["tv_play_rank"] = int(tv_rank_num)
 tv_item["tv_play_category"] = tv_category
 tv_item["tv_play_state"] = tv_status
 tv_item["tv_play_update_day"] = tv_update_day
 tv_item["tv_play_return_date"] = tv_return_date
 tv_item["tv_play_url"] = tv_url
 tv_item["tv_play_cur_season"] = 0
 yield tv_item
 # 正常信息列表
 content_div = soup.find_all(name="tr", attrs={"class": re.compile(r"Scontent")})
 for item in content_div:
 td_list = item.find_all(name="td")
 tv_rank_num = td_list[0].text
 link_a = td_list[1].find(name="a")
 tv_url = self.root_url + link_a["href"]
 tv_name = link_a.text
 tv_category = td_list[2].text.strip()
 tv_status = td_list[3].text.strip()
 tv_update_day = td_list[4].text.strip()
 tv_return_date = td_list[5].text.strip()
 tv_item = TtmjTvPlayItem()
 tv_item["tv_play_name"] = tv_name
 tv_item["tv_play_rank"] = int(tv_rank_num)
 tv_item["tv_play_category"] = tv_category
 tv_item["tv_play_state"] = tv_status
 tv_item["tv_play_update_day"] = tv_update_day
 tv_item["tv_play_return_date"] = tv_return_date
 tv_item["tv_play_url"] = tv_url
 tv_item["tv_play_cur_season"] = 0
 yield tv_item
 next_page_ul = soup.find_all(name="ul", attrs={"class": "pagination"})
 if len(next_page_ul) != 0:
 last_page_a = next_page_ul[0].find_all(name="a", attrs={"class": "end"})
 if len(last_page_a) != 0 and max_page_num == -1:
 max_page_num = last_page_a[0].text
 if int(cur_page) < int(max_page_num):
 next_page_num = int(cur_page) + 1
 else:
 logging.info("ALl finished")
 return
 next_page_url = cur_url[:-(len(cur_url.split("/")[-1]))] + str(next_page_num) + ".html"
 yield Request(url=next_page_url, callback=self.parse_page, dont_filter=True, meta={"cur_page": next_page_num, "max_page_num": max_page_num})

代码简单说一下,通过 【Chrome】--【检查】页面,看到我们要找的列表信息标签。

然后,通过 BeautifulSoup 来解析找到相对应的文字,并且解析成我们想要得到的 Scrapy Item ,最后在 pipeline 里面做存入数据库的操作。

那我们接下来就运行一下这个 Scrapy 框架写的爬取 35 页信息的爬虫,看看效果如何。

数据库里面看到已经存入了数据:

从结果里面看到,用 Scrapy ,没有修改 setting.py 文件,爬取 35 页数据,然后生成 Scrap.Item ,总共用了 2 分 10 秒。成绩还可以哈。

4、aiohttp撸法撸起来

这里,用网上的一张图来给大家看一下 aiohttp 的流程:

其实 aiohttp 就是讲事件进入一个队列,然后挨个调用执行,这些任务有个共同的特点,就是他们需要等待操作。所以,在等待的过程中,程序会调起其他任务接着执行。

我们来看代码:

sem = asyncio.Semaphore(80) # 信号量,控制协程数,防止爬的过快
client = pymongo.MongoClient("mongodb://xx.xx.xx.xx/", xxx)
db = client["xxx"]
ttmj_collection = db["xxx"]
result_dict = list()
def generateRequestList(url, start, end):
 page_list = list()
 for i in range(start, end):
 genUrl = url.replace("**", str(i))
 page_list.append(genUrl)
 return page_list
async def grab_page(url):
 with(await sem):
 async with aiohttp.ClientSession() as session:
 content = await fetch(session, url, 0)
async def fetch(session, url, level, tv_item=None):
 async with session.get(url) as req:
 content = await req.text()
 soup = BeautifulSoup(content, "html.parser")
 root_url = "http://www.ttmeiju.vip"
 cur_time_string = datetime.datetime.now().strftime('%Y-%m-%d')
 rank_top_3_div = soup.find_all(name="div", attrs={"class": "ranktop3"})
 for item in rank_top_3_div:
 link_a = item.find_all(name="a")[0]
 tv_url = root_url + link_a["href"]
 tv_name = link_a.text
 tv_rank_num = item.find_all(name="div", attrs={"class": "ranknum"})[0].text
 play_info_div = item.find_all(name="div", attrs={"class": "mjinfo"})
 play_info_one = play_info_div[0].text
 play_info_two = play_info_div[1].text
 tv_category = play_info_one.split("/")[0].strip()
 tv_status = play_info_one.split("/")[1].strip()
 tv_update_day = play_info_one.split("/")[2].split(":")[-1].strip()
 temp_result = re.findall("\d{4}-\d{2}-\d{2}", play_info_two)
 if len(temp_result) != 0:
 tv_return_date = temp_result[0]
 else:
 tv_return_date = "暂无"
 tv_item = TtmjTvPlayItem()
 tv_item.tv_play_name = tv_name
 tv_item.tv_play_rank = int(tv_rank_num)
 tv_item.tv_play_category = tv_category
 tv_item.tv_play_state = tv_status
 tv_item.tv_play_update_day = tv_update_day
 tv_item.tv_play_return_date = tv_return_date
 tv_item.tv_play_update_time = cur_time_string
 tv_item.tv_play_url = tv_url
 tv_item_dict = dict(
 (name, getattr(tv_item, name)) for name in dir(tv_item) if not name.startswith('__'))
 # print("complete Item: %s" % (tv_item.tv_play_name))
 result_dict.append(tv_item_dict)
 # await fetch(session, tv_url, 1, tv_item)
 content_div = soup.find_all(name="tr", attrs={"class": re.compile(r"Scontent")})
 for item in content_div:
 td_list = item.find_all(name="td")
 tv_rank_num = td_list[0].text
 link_a = td_list[1].find(name="a")
 tv_url = root_url + link_a["href"]
 tv_name = link_a.text
 tv_category = td_list[2].text.strip()
 tv_status = td_list[3].text.strip()
 tv_update_day = td_list[4].text.strip()
 tv_return_date = td_list[5].text.strip()
 tv_item = TtmjTvPlayItem()
 tv_item.tv_play_name = tv_name
 tv_item.tv_play_name_en = tv_url.split("/")[-1].replace(".", " ")[:-5]
 tv_item.tv_play_name_en_dot = tv_url.split("/")[-1][:-5]
 tv_item.tv_play_name_ch = tv_name.split(" ")[0]
 tv_item.tv_play_rank = int(tv_rank_num)
 tv_item.tv_play_category = tv_category
 tv_item.tv_play_state = tv_status
 tv_item.tv_play_update_day = tv_update_day
 tv_item.tv_play_return_date = tv_return_date
 tv_item.tv_play_url = tv_url
 tv_item.tv_play_cur_season = 0
 tv_item_dict = dict(
 (name, getattr(tv_item, name)) for name in dir(tv_item) if not name.startswith('__'))
 print("complete Item: %s" % (tv_item.tv_play_name))
 result_dict.append(tv_item_dict)
def main_grab(page_list):
 loop = asyncio.get_event_loop() # 获取事件循环
 tasks = [grab_page(url) for url in page_list] # 把所有任务放到一个列表中
 loop.run_until_complete(asyncio.wait(tasks)) # 激活协程
 loop.close() # 关闭事件循环
def writeToDb():
 for tv_item in result_dict:
 ttmj_collection.insert(tv_item)
 print("insert item: " + tv_item["tv_play_name"])
 client.close()
if __name__ == '__main__':
 start_url = "http://www.ttmeiju.vip/index.php/summary/index/p/**.html"
 page_list = generateRequestList(start_url, 1, 36)
 start = time.time()
 main_grab(page_list)
 print('爬取总耗时:%.5f秒' % float(time.time() - start))
 writeToDb()
 print('总耗时:%.5f秒' % float(time.time() - start))

aiohttp的关键写法,就是在开头,得声明信号量,这里申请的是 80 个。

接着就是 main_grab 方法中,开始调用 aiohttp。 aiohttp的方法,都需要以 async def 开头来定义,其中,需要等待的地方,可以用 await 来写。皮爷的这个代码,你完全可以照猫画虎的写出自己的逻辑。如果还有什么不懂的,自己百度或者谷歌搜索 aiohttp 就可以,网上例子一大堆,都很简单,看了也没啥用。还不如实际的撸个项目,加深体验。

我们来看结果,爬取35个网页总共用了 2 秒多:

你没看错,单纯的爬取网页,就 2 秒。

数据库中是:

插入数据库,皮爷是一条一条插入的,所以这个耗时很严重,导致整个工程运行了 35 秒:

从之前的 130 秒,到现在的 35 秒,你说速度是不是快了很多???你说快不快?是不是比刘翔还快?? 接下来快看骚操作怎么搞。

5、最后总结

爬虫用 aiohttp 来写还是用 Scrapy 来写,自己定夺,他们各有各的好处。

Scrapy框架完整,结果清晰;

aiohttp 速度更快,非常灵活。

所以,想用什么写爬虫,要根据你自己的需求来定。但是皮爷最近搞的东西,打算用 aiohttp 来自己做一套框架,来专门为自己使用。

欢迎大家加入小编创建的Python行业交流群,有大牛答疑,有资源共享,有企业招人!是一个非常不错的交流基地!群号:556370268

猜你喜欢

转载自blog.csdn.net/qq_39345165/article/details/88088001
今日推荐