問題描述:在爬取一些反爬機制作的比較好的網站時,常常會碰見一個問題就網站代碼是經過js寫的,這種就沒法直接使用通常的爬蟲工具爬取,這種狀況通常有兩種解決方案html
第一種:把js代碼轉爲html代碼,而後再使用html代碼解析工具爬取,目前經常使用的工具是selenium和scrapy-splash,我使用的是第一個工具,第二個還有搞個docker服務,太麻煩程序員
第二種:本身觀察js代碼,找到存放數據的地方,直接獲取,這種方式須要有js基礎,反正我看到一堆亂七八糟的js就頭大,這種方式passweb
下面就是第一種實現方式:redis
技術架構:scrapy-redis + selenium + webdriverdocker
解釋:使用scrapy-redis進行分佈式爬蟲效率高,並且直接把url放到redis中,這種方式對於請求連接的管理很是簡單, selenium這工具能夠直接融入到scrapy中,做爲一箇中間件,至於這個中間件的原理,網上有不少資料,其實原理很簡單,就是每次請求進來,先讓selenium這中間件處理一下,把js代碼轉爲html,而後直接return一個對象給spider進行爬蟲,這個對象裏面放的就是html,api
下面就是這個中間件的代碼:瀏覽器
class SeleniumMiddleware(object): def __init__(self,timeout=25): profile = FirefoxProfile() profile.set_preference('permissions.default.image', 2) self.browser = webdriver.Firefox(profile) self.timeout = timeout self.browser.maximize_window() # # self.browser.implicitly_wait(20) self.browser.set_page_load_timeout(self.timeout) self.wait = WebDriverWait(self.browser, self.timeout) def __del__(self): self.browser.close() def process_request(self, request, spider): """ 用WebDriver抓取頁面 :param request: Request對象 :param spider: Spider對象 :return: HtmlResponse """ logging.info('******WebDriver is Starting******') try: #這裏的ip和port能夠根據本身的狀況填充,好比經過api獲取的代理ip,或者從代理池中獲取也能夠 ip = '60.182.17.174' port = '4224' #user_agent仍然能夠動態修改,這裏測試寫死,網上有不少每次請求隨機修改代理的user-agent的方法 user_agent ='Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)' self.browser.get("about:config") script = ''' var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch); prefs.setIntPref("network.proxy.type", 1); prefs.setCharPref("network.proxy.http", "{ip}"); prefs.setIntPref("network.proxy.http_port", "{port}"); prefs.setCharPref("network.proxy.ssl", "{ip}"); prefs.setIntPref("network.proxy.ssl_port", "{port}"); prefs.setCharPref("network.proxy.ftp", "{ip}"); prefs.setIntPref("network.proxy.ftp_port", "{port}"); prefs.setBoolPref("general.useragent.site_specific_overrides",true); prefs.setBoolPref("general.useragent.updates.enabled",true); prefs.setCharPref("general.useragent.override","{user_agent}"); '''.format(ip = ip,port=port,user_agent=user_agent) self.browser.execute_script(script); time.sleep(1); self.browser.get(request.url) self.wait.until(EC.presence_of_element_located((By.XPATH, '//div[@class="s-result-list sg-row"]'))) return HtmlResponse(url=request.url, body=self.browser.page_source, request=request, encoding='utf-8', status=200) except TimeoutException: return HtmlResponse(url=request.url, status=500, request=request)
-------------------------------------------------姑娘滑溜溜的馬甲線------------------------------------------------------
注意:這是網上目前能夠找到的惟一個完整代碼的解決方案,能夠直接複製粘貼,上面都沒有說重點,其實這裏最重要的就是動態修改代理ip,網上不少資料都是當瀏覽器啓動的時候指定代理ip,那若是想要更換代理ip,很差意思,重啓瀏覽器,這種方式效率很是低,對於一個有追求的程序員來講就是種恥辱
而後把這個中間件配置到settings中:
DOWNLOADER_MIDDLEWARES = { 'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware' : None, # 必需 ,禁用默認的middleware 'amazon.custom_rewrite.SeleniumMiddlewares.SeleniumMiddleware': 541, #自定義selenium中間件 }
-------------------------------------------------姑娘滑溜溜的馬甲線------------------------------------------------------
更新:上面只是解決了動態代理ip的問題,那如何解決動態修改瀏覽器頭呢,很簡單,只須要在上面的js中添加
prefs.setBoolPref("general.useragent.site_specific_overrides",true); prefs.setBoolPref("general.useragent.updates.enabled",true); prefs.setCharPref("general.useragent.override","{user_agent}");
-------------------------------------------------姑娘滑溜溜的馬甲線------------------------------------------------------
2019-04-17更新:緩存
上面的配置在運行的過程當中,瀏覽器通常運行幾天以後就會崩潰, 我定位了好久才發現是瀏覽器內存泄露致使的,由於firefox瀏覽器默認是可使用緩存的,隨着爬蟲的運行,這就會使瀏覽器的緩存愈來愈大,從而致使內存架構
泄露,那怎麼解決呢?很簡單,直接把緩存給禁用了就能夠,不過有的爬蟲須要用緩存加快爬蟲的速度,這種狀況下我尚未想到好的處理辦法,一個思路是定時啓動瀏覽器,好比定時5個小時重啓一次瀏覽器,可是這樣子有點麻煩吧,下面是禁用scrapy
緩存的代碼
prefs.setBoolPref("browser.cache.disk.enable", false); prefs.setBoolPref("browser.cache.memory.enable", false); prefs.setBoolPref("browser.cache.offline.enable", false);
說明:火狐瀏覽器從25版本以後就已經在about:config中沒法找到general.useragent.override屬性了,解決辦法就是在about:config右鍵,新建-->字符串,添加這個屬性就能夠