簡介
scrapy是基於python的爬蟲框架,易於學習與使用。本篇文章主要介紹如何使用scrapy爬取鼠繪漫畫網海賊王最新一集的漫畫。
源碼參見:https://github.com/liudaolufei/crawl-comic
網站分析
鼠繪海賊王網站網址為:http://www.ishuhui.com/comics/anime/1
漫畫鏈接無法直接從原始網頁中得到,需要點擊對應的話數,鏈接才會顯示出來,如下圖所示:
獲取鏈接后即可獲得海賊王漫畫的網頁地址,網頁如下:
原始的網頁沒有漫畫的圖片鏈接,需要點擊圖中的“連頁模式”,之后網頁才會顯示漫畫的具體內容,此時的漫畫內容也是動態加載的,隨着窗口不斷往下,圖片會一張張加載出來。
工具
scrapy: 爬蟲框架
splash: 動態網頁處理
環境安裝
scrapy安裝
已安裝anaconda,python3.7版本。
創建scrapy虛擬環境並激活
1 conda create -n scrapy 2 conda activate scrapy
使用pip安裝scrapy以及用於與splash交互的scrapy-splash
1 pip install scrapy scrapy-splash
在使用scrapy的ImagePipeline的時候需要第三方庫PIL,安裝PIL
1 pip install pillow
splash安裝
splash服務是通過docker啟用的,所以要先安裝docker,docker安裝參見:https://docs.docker.com/install/
使用docker安裝splash
1 docker pull scrapinghub/splash
啟用splash服務
1 docker run -p 8050:8050 scrapinghub/splash
scrapy爬蟲
生成scrapy項目並生成爬蟲
1 scrapy startproject crawlComics 2 cd crawlComics 3 scrapy genspider shuhui http://www.ishuhui.com/anime/1
此時在spiders文件夾下有了shuhui.py,這是爬蟲的基礎模板。
修改基礎設置(setting.py)
使用splash的設定
1 # Splash服務器地址 2 SPLASH_URL = 'http://localhost:8050' 3 # 開啟Splash的兩個下載中間件並調整HttpCompressionMiddleware的次序 4 DOWNLOADER_MIDDLEWARES = { 5 'scrapy_splash.SplashCookiesMiddleware': 723, 6 'scrapy_splash.SplashMiddleware': 725, 7 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 727, 8 } 9 # 設置去重過濾器 10 DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
user-agent
1 USER_AGENT = "Mozilla/5.0 (Platform; Encryption; OS-or-CPU; Language) AppleWebKit/AppleWebKitVersion (KHTML, like Gecko)"
使用ImagePipelines保存圖片的相關設定
1 # 圖片存儲位置 2 IMAGES_STORE = "/home/luoheng/comics/" 3 # 啟用ImagesPipeline來下載圖片 4 ITEM_PIPELINES = { 5 'crawlComics.pipelines.RenameImagesPipeline': 500, 6 }
添加item
item是scrapy中傳輸數據的基本單位,可以簡單的理解為字典。
scrapy的ImagePipelines是為下載圖片專門設計的,它接受一個包含鍵值“image_urls”和“images”的item。ImagePipelines在得到item后,會自動根據image_urls中包含的鏈接下載對應的圖片,並放置到上文設定的IMAGE_STORE中。
故為了使用ImagePipelines,在items.py中添加一個符合條件的item:
1 class ImageItem(scrapy.Item): 2 image_urls = scrapy.Field() 3 images = scrapy.Field() 4 # store picture names 5 image_names = scrapy.Field()
前兩項是為了滿足ImagePipelines的要求設置的,最后一項用於設置下載圖片的名字,因為ImagePipelines默認使用hash值對圖片進行命名,不具可讀性。
實現爬蟲
爬蟲主要的工作流程
1. 在http://www.ishuhui.com/anime/1頁面點擊最新漫畫按鈕,返回響應結果
2. 從響應結果中獲取海賊王最新漫畫的具體網址
3. 在最新漫畫的網址中點擊“連頁模式”,並將網頁拉到最下方,以使所有圖片的鏈接加載出來,返回響應結果
4. 從響應結果中獲取圖片的鏈接,使用ImagePipelines下載
導入item與splash
1 from scrapy_splash import SplashRequest 2 from ..items import ImageItem
SplashRequest用於與splash服務進行交互,在這里主要用了splash的“execute”功能,它接受的幾個參數如下:
1 SplashRequest(url, callback=self.parse, endpoint="execute", args={"lua_source": lua_script})
url是原始網址,callback是回調函數,即處理返回的response的函數,endpoint指明使用的功能,這里使用的是“execute”,即執行腳本的功能,最后一個args用於指定用於執行的腳本,這里的腳本是用lua語言寫成的,在Python中以字符串形式存在。
點擊最新漫畫
1 click_latest_comic = """ 2 function main(splash) 3 # 打開網頁 4 splash:go(splash.args.url) 5 # 等待網頁加載 6 splash:wait(2) 7 # 點擊最新漫畫(由class ant-tag-red識別) 8 splash:runjs("document.getElementsByClassName('ant-tag-red')[0].click()") 9 # 等待網頁加載 10 splash:wait(0.1) 11 # 返回網頁結果 12 return splash:html() 13 end 14 """ 15 # 返回點擊最新漫畫后的響應結果 16 yield SplashRequest(url, callback=self.show_all, endpoint="execute", args={"lua_source": click_latest_comic})
打開具體網址,並加載所有漫畫圖片
show_all_pictures = """ function main(splash) # 打開網頁 splash:go(splash.args.url) # 等待網頁加載 splash:wait(2) # 點擊連頁模式(由class z-page識別) splash:runjs("document.getElementsByClassName('z-page')[0].click()") # 等待網頁加載 splash:wait(0.1) # 將網頁拉到最下方,使所有圖片的鏈接加載出來 splash:runjs("window.scrollTo(0, document.body.scrollHeight)") # 等待網頁加載 splash:wait(0.1) # 返回響應結果 return splash:html() end """ def show_all(self, response): # 獲取漫畫具體網址 target = response.css(".m-comics-num-link").xpath("@url").extract_first() # target是相對網址,它的前綴是http://www.hanhuazu.cc new_url = "http://www.hanhuazu.cc" + target # 返回已經加載了所有圖片鏈接的網頁 yield SplashRequest(new_url, endpoint="execute", args={"lua_source": show_all_pictures})
使用ImageItem保存圖片鏈接並傳送給ImagePipelines下載
1 def parse(self, response): 2 # 所有圖片 3 comics = response.css("div img") 4 # src屬性中保存所有圖片鏈接,alt屬性保存圖片名字 5 comics_picture, comics_name = comics.xpath("@src").extract(), comics.xpath("@alt").extract() 6 # 構建ImageItem並返回給ImagePipelines 7 images = ImageItem() 8 # 保存圖片鏈接 9 images["image_urls"] = comics_picture 10 # 保存圖片鏈接與圖片名字的映射,用於下載圖片的命名 11 images["image_names"] = dict(zip(comics_picture, comics_name)) 12 # 返回后,ImagePipelines會自動下載圖片 13 return images
修改ImagePipelines,覆蓋默認命名
1 class RenameImagesPipeline(ImagesPipeline): 2 """to rename the images properly""" 3 4 5 def process_item(self, item, spider): 6 # add names 7 self.item_names = item["image_names"] 8 info = self.spiderinfo 9 requests = arg_to_iter(self.get_media_requests(item, info)) 10 dlist = [self._process_request(r, info) for r in requests] 11 dfd = DeferredList(dlist, consumeErrors=1) 12 return dfd.addCallback(self.item_completed, item, info) 13 14 def file_path(self, request, response=None, info=None): 15 return self.item_names[request.url]
ImagesPipeline的file_path用於計算圖片的命名,但是由於圖片名字保存在item的image_names屬性中,而file_path並不直接接受item參數,所以需要將item["image_names"]保存到self.item_names中,如此file_path才可訪問到名字。
因此將ImagesPipeline的process_item方法的代碼完整復制過來,並在最前面將image_names保存到self.item_names當中,在file_path方法中只用簡單調用self.item_names[request.url]即可獲取圖片名字。
運行爬蟲
1 scrapy crawl shuhui
完成后,在comics文件夾中可以看到爬取的圖片。
總結
以爬取鼠繪海賊王漫畫為例,本文簡單介紹了使用scrapy+splash處理動態網頁的方法。