Python爬蟲加速神器的牛刀小試,就問你怕不怕

大名鼎鼎的aiohttp,相信若是你學習Python或者爬蟲的時候,確定據說過這個東西。沒聽過也沒關係,今天看完文章,只要記住,aiohttp這個東西,在寫爬蟲的時候,很牛逼就好了。php

aiohttp 就是一個用 asyncio實現的 HTTP client/server。 你能夠經過它來簡單實現一個具備異步處理功能的 clients 和 servers。 aiohttp同時還支持 Server WebSockets 和 Client WebSockets,讓你使用起來更加簡單。html

光明正大的打廣告linux

六十四卦小程序
六十四卦小程序

今天,皮爺就帶你來體驗一下,這個「爬蟲加速器」。mongodb

0x00 咱們的爬蟲需求

皮爺最近在作一個項目,就是用微信小程序追美劇的項目,那麼首先,咱們得須要有一個全部美劇的來源,恰巧,下面這個排行榜就有咱們全部須要的信息:數據庫

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

初級要求

咱們很簡單,就是須要從【第一頁】:微信小程序

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

一直爬到最後一頁【第三十五頁】:bash

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

中級要求

因爲排行榜頁面沒有美劇的【季】信息,這個必須進入詳情頁來作,因此,中級要求就是針對每一條美劇,進入詳情頁,從裏面爬取出來當前美劇的【季】信息。

這個要求不難吧?就是一級頁面變換 page number 的數值來爬取信息。就算要爬取【季】信息,咱們的爬蟲深度也就才兩級。

因此,這個需求不難。並且網頁都是靜態資源,通常簡單的爬蟲程序就能hou住。

0x01 擼碼前的整理

這一步,咱們須要想一想經過什麼樣的方法可以實現上面的需求。

熟悉皮爺的童鞋都知道,皮爺以前的爬蟲程序主要用 Scrapy 這個框架。爲啥主要用這個?主要這個是一個框架。框架的意思就是寫起來簡單。何爲簡單?你只須要專一寫爬蟲的相關邏輯部分就好,不須要管理程序的生命週期,代碼控制之類的問題,由於框架都給你整理好了。

那麼,咱們的需求就能夠用兩種作法來搞:

  1. 用 Scrapy 來寫。
  2. 本身寫爬蟲,可是要用到 aiohttp 的東西。

下面皮爺就簡單爲你們來講一下他們是怎麼實現的,以及最後對比結果。

0x02 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 秒。成績還能夠哈。

0x03 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_listasync 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 秒,你說速度是否是快了不少???你說快不快?是否是比劉翔還快?? 接下來快看騷操做怎麼搞。

0x04 騷操做福利

騷操做,就要騷起來。
你看皮爺用 aiohttp 寫的Python運行起來是否是很給力?不但爬取數據,還能將數據結果存儲到服務器裏面。你有沒有想過,這個代碼是否是能夠放到服務器上面讓服務器本身跑???

答案固然是:能夠的!!!

沒錯,你之後寫的 py 文件,都可以放到服務器上面自動執行。再也不須要像如今這樣,本身寫了代碼,在ide裏面跑一邊以後,就荒廢了。

那麼問題來了,首先,你是否是得有個服務器啊?皮爺不虧待大家,特地給大家準備了優惠券,有沒有的均可以來領取。

阿里雲部分
【阿里雲新人1888元雲產品通用代金券】:
promotion.aliyun.com/ntms/yunpar…

【阿里雲爆款雲主機,2折優惠券】:
promotion.aliyun.com/ntms/act/qw…

【阿里雲企業級服務器2折優惠券】:
promotion.aliyun.com/ntms/act/en…

騰訊雲

【新客戶無門檻領取總價值高達2775元代金券,每種代金券限量500張,先到先得】:
cloud.tencent.com/redirect.ph…

【騰訊雲服務器、雲數據庫特惠,3折優惠券】:
cloud.tencent.com/redirect.ph…

有了服務器,那麼將本地文件上傳到服務器上面,只須要用 scp 命令就好:

$ scp <本地文件路徑> <服務器角色>@<服務器ip地址>:<服務器文件路徑> 複製代碼

上傳代碼參考文章

那麼怎麼定是執行呢?服務器通常都是 linux 系統,linux 系統自帶一個命令叫 crontab ,用這個命令就能夠定製執行了。

這一套組合拳打下來,你說騷不騷?

0x05 最後總結

爬蟲用 aiohttp 來寫仍是用 Scrapy 來寫,本身定奪,他們各有各的好處。

Scrapy框架完整,結果清晰;

aiohttp 速度更快,很是靈活。

因此,想用什麼寫爬蟲,要根據你本身的需求來定。可是皮爺最近搞的東西,打算用 aiohttp 來本身作一套框架,來專門爲本身使用。

『皮爺擼碼』是一個很硬核的公衆號,想要獲取更多代碼細節,歡迎關注『皮爺擼碼』,裏面能夠加入到微信羣,和你們一塊兒聊技術。

因此,關注我,你不吃虧。

相關文章
相關標籤/搜索