Facebook 爬蟲



title: Facebook 爬蟲
tags: [python3, facebook, scrapy, splash, 爬蟲]
date: 2018-06-02 09:42:06
categories: python
keywords: python3, facebook, scrapy, splash, 爬蟲

初次接觸到scrapy是公司要求編寫一個能夠解析JavaScript的爬蟲爬取鏈接的時候聽過過,當時我當時覺得它並不適合這個項目所以放棄這個方案,時隔一年多公司有了爬取Facebook用戶信息的需求,這樣才讓我正式接觸並使用到scrapy

需求

  1. 首先從文件或者數據庫導入第一批用戶做為頂層用戶,並爬取頂層用戶好友的發帖信息包括其中的圖片
  2. 將第一步中爬取到的用戶好友作為第二層用戶並爬取它們的發帖信息和好友信息
  3. 將第二層用戶中爬到的好友作為第三層用戶並爬取它們的好友信息

也就是說不斷爬取用戶的好友和它的發帖信息直到第三層為止

根據這個需求首先來確定相關方案

  1. 爬蟲框架使用scrapy + splash:Facebook中大量采用異步加載,如果簡單收發包必定很多內容是解析不到的,因此這里需要一個JavaScript渲染引擎,這個引擎可以使用selenium + chrome(handless) 這套,但是根據網上一位老哥的博客我知道了splash這種東西,在做相關比較之后我選擇了使用splash,主要的理由有以下幾點:

a. 與selenium比較起來,它的官方文檔更為全面

b. 支持異步的方式,這個可以與scrapy的異步回調方式完美結合並充分發揮性能

c. 它提供了一套與scrapy結合的封裝庫,可以像scrapy直接yield request對象即可,使用方式與scrapy類似降低了學習成本

d. 它提供了lua腳本的方式可以很方便的操作瀏覽器對象

e. 跨平台。相比於使用chrome作為渲染工具,它可以直接執行在Linux平台

在scrapy中使用splash時可以安裝對應的封裝庫scrapy_splash,這個庫的安裝配置以及使用網上基本都有詳細的講解內容,這里就不再提了

當然它也有缺點,但是這並不在討論之中,至於具體如何選擇就是一個見仁見智的問題了
2. 開發語言: python3 ,python在開發爬蟲方面有獨特的優勢,這個就不用我多說了,弄過爬蟲的朋友都知道
3. 開發工具 pycharm, JB的pycharm幾乎是Python IDE的首選

設計與實現

這里可能涉及到商業秘密,畢竟是簽過保密協議的,所以在這部分我不會放出完整的代碼,只會提供一個思路然后給出部分關鍵代碼以供參考。這里我想根據我遇到的問題,以問題的方式來講述這個項目,畢竟對於爬蟲、框架這些東西大家都很熟再來講這些也沒有多大意思了

用戶登錄

在瀏覽器中操作的時候發現,如果是游客(也就是未登陸狀態)的時候,當我們瀏覽相關用戶的時間線時會得到下面這個界面
未登錄情況下查看用戶信息
在未登錄的情況下查看用戶信息的時候會彈出一個界面需要登錄或者注冊。因此從這里來看爬蟲的第一個任務就應該是登錄
登錄的時候scrapy提供了一個form_response的方法可以很方便的填寫表單並提交,但是我發現用這種方式只能在返回的response對象中的request.headers里面找到cookie的字符串,而由於splash需要我們傳入cookie的字典形式,這里我沒有找到什么很好的辦法,只能是采用splash 提供的方法。
Facebook中登錄頁面為https://www.facebook/login。因此我重載爬蟲的start_requests方法,提交一個針對這個登錄頁面url的請求。這個頁面不涉及到渲染問題自然就使用Requests對象

def start_requests(self):
    #開啟爬取之前先登錄
    yield Request(
        url= self.login_url, # https://www.facebook.com/login
        callback= self.login,
    )

當它請求的頁面返回時觸發login方法,在這個方法中我們提供了一個lua腳本自動填寫用戶名密碼,然后提交請求,並最終返回成功的cookie

yield SplashFormRequest.from_response(
    response,
    url = self.login_url,
    formdata={
        "email":user,
        "pass": password
    },
    endpoint="execute",
    args={
        "wait": 30,
        "lua_source": lua_script, #這個參數是一個lua腳本的字符串
        "user_name" : user,  #user和password將會作為參數傳入到lua腳本中
        "user_passwd" : password,
    },
    callback = self.after_login,
    errback = self.error_parse,
)

這里我們使用splash來發送請求包,這里我們主要向lua腳本中傳入用戶名和密碼,下面是lua腳本的相關內容

function main(splash, args)
    local ok, reason = splash:go(args.url)
    user_name = args.user_name
    user_passwd = args.user_passwd
    user_text = splash:select("#email")
    pass_text = splash:select("#pass")
    login_btn = splash:select("#loginbutton")
    if (user_text and pass_text and login_btn) then
        user_text:send_text(user_name)
        pass_text:send_text(user_passwd)
        login_btn:mouse_click({})
    end

    splash:wait(math.random(5, 10))
    return {
        url = splash:url(),
        cookies = splash:get_cookies(),
        headers = splash.args.headers,
      }
end

根據相關資料,SplashRequest 函數中的參數將會以lua table的形式被傳入到splash形參中,而函數的args參數中的內容以 table的形式被傳入到形參args中,所以這里要獲取到用戶名和密碼只需要從args里面取即可
上述lua代碼首先請求對應的登錄界面(我覺得這里應該不用請求,而直接使用response,但是這是我在寫這篇文章的時候想到的還沒有驗證),然后通過css選擇器找到填寫用戶名,密碼的輸入框和提交按鈕。然后填寫相關內容,最后點擊按鈕進行登錄,然后等待一定時間,這里一定要等待以便Facebook服務器驗證並跳轉到對應的鏈接,最后我們是通過鏈接來判斷是否登錄成功。最后返回當前頁面的url,cookie和對應的頭信息
在瀏覽器中執行登錄操作的時候發現如果是新用戶(沒有填寫相關信息用戶)會跳轉到www.facebook.com/?sk=welcome這個頁面要求用戶填入一定的信息,而老用戶則會跳轉到www.facebook.com 這個url,這個頁面會顯示用戶關注的好友動態。因此在程序中我也根據跳轉的新頁面是否是這兩個頁面來進行判斷是否登錄成功的.登錄成功后將腳本返回的cookie保存,腳本返回的信息在scrapy的response.data中作為字典的形式保存

代理

由於眾所周知的原因,Facebook對於國內來說是一個404的站點,國內用戶想要訪問必須提供一個代理。
在scrapy中代理可以設置在對應的下載中間件中,在下載中間件的process_request函數中設置request.meta["proxy"] = proxy
但是這種方式針對splash時就不管用了,我找了很多資料發現可以在lua腳本中設置,每次在執行之前都需要相同的代碼來設置代理,因此我們可以采用下面的模板

function main(splash, args)
    splash:on_request(function(request)
        request:set_proxy{
            host = '0.0.0.0', --代理服務器的IP
            port = 0, --代理服務器的端口
            username = '', --登錄的用戶名和密碼
            password = '',
            type = "http", -- 代理的協議,根據官網的說法目前只支持http和ss5 ,目前就這個項目來說http就夠了
          }
		 end)

     --do something
end

每次執行含有這段代碼的腳本時首先執行on_request函數設置代理的相關信息,然后執行splash:go函數時就可以使用上面的配置訪問對應站點了

使爬蟲保持登錄狀態

根據splash的官方文檔的說明,splash其實可以看做一個干凈的瀏覽器,就好像我們在使用瀏覽器每次請求一個新頁面的時候同時清理了里面的緩存一樣,它不會保存之前的任何狀態,所以這里的cookie只能每次在發包的同時給它設置上,好在splash給了相應的方法來設置和獲取它,下面是關於cookie的模板

local cookies = splash:get_cookies()
-- ... do something ...
splash:init_cookies(cookies)  -- restore cookies

至此我們的lua腳本的模板就變成了這樣

function main(splash, args)
    splash:init_cookies(splash.cookies) -- 這個cookie是通過SplashRequest函數的cookies參數傳入的
    splash:on_request(function(request)
        request:set_proxy{
            host = '0.0.0.0', --代理服務器的IP
            port = 0, --代理服務器的端口
            username = '', --登錄的用戶名和密碼
            password = '',
            type = "http", -- 代理的協議,根據官網的說法目前只支持http和ss5 ,目前就這個項目來說http就夠了
          }
		 end)

     --do something

     return {
       cookie = splash:splash:get_cookies()
     }
end

獲取用戶主頁面

我們在Facebook隨便點擊一個用戶進入它的主頁面,查看url如下
英文名稱

這里寫圖片描述
可以看到針對用戶名為英文的情況,它簡單的將英文名作為二級目錄,只不過將空格換成了點,而針對不為英文的用戶,它以profile作為二級目錄,並且后面帶上一個參數id,這個ID就是用戶的ID。其實根據后面我自己的實驗不管上面的哪種用戶都可以通過這個ID訪問到,我們可以組成一個url:https://www.facebook.com/[id] 來訪問用戶首頁,因此項目中要求提供的外部導入用戶名必須是英文或者是ID,以便能直接通過url拼接的方式來獲取用戶首頁
除了這個區別之外,還有一種稱之為公共主頁的頁面,比如下面是特朗普的公共主頁
這里寫圖片描述
對於公共主頁來說它沒有好友信息,沒有時間線,因此針對這種頁面的信息的解析可能需要別的方法。而光從url、id、和頁面內容來看很難區分,而我在查找獲取Facebook用戶ID的相關內容的時候碰巧找到了它的區分方法,公共主頁的HTML代碼中只有一個page_id和profile_id,而個人的只有profile_id 其中用戶ID就是這個profile_id
比如下面分別是一個個人主頁和公共主頁搜索page_id的結果
個人主頁page_id搜索結果

公共主頁page_id搜索結果
從上面的結果來看個人用戶中page_id 只會出現在注釋中,這是用瀏覽器請求的結果,其實在實際使用爬蟲爬取到的結果中是搜不到這個id的,我們可以根據這個特性來區分,並且獲取這兩種主頁的ID

def _get_user_info(self, html, url):
    key = "page_id=(\d+)" # 使用正則表達式獲取page_id后面的id值
    # page_id 只會出現在公共主頁上,所以根據page_id來判斷頁面類型
      pattern = re.compile(key)
      it = pattern.finditer(html)
      user_type = 0

      try:
          it = next(it) #如果未找到這個地方會報StopIteration異常
          user_type = TopUser.PUBLIC_PAGE # 公共主頁

      except StopIteration:
          # 未找到page_id 此時視為個人主頁
          key = "profile_id=(\d+)" #個人主頁的id是profile_id的值
          pattern = re.compile(key)
          it = pattern.finditer(html)
          try:
              it = next(it)
              user_type = TopUser.PRIVATE_PAGE
          except StopIteration:
              # 兩個都沒找到,此時視為頁面錯誤,返回錯誤,爬蟲停止
              pass

      #TODO:解析對應的用戶信息,這里主要解析用戶id和頁面類型

獲取時間線信息

Facebook的用戶時間線是通過異步加載的方式來進行的,我使用Chrome分析過它發送的異步請求,發現它里面是經過了加密的,因此不能通過解析它的響應包來獲取相關信息,但是我們有splash這一大殺器,它就是一個瀏覽器,一般在加載更多信息的時候都會執行下來操作,所以說這里我們只要模擬這個下拉的操作就可以了,要操作這個瀏覽器當然是使用lua腳本了,下面是對應的lua腳本

function main(splash, args)
    --前面是設置cookie和代理的操作
    local ok, reason = splash:go(args.url)
    splash:wait(math.random(5, 10))
    html  = splash:html()
    old_html = ""
    flush_times = args.flush_times --這里是下拉次數,就好像操作瀏覽器一樣每次下拉就會加載新的內容
    i = 0

    while(html ~= old_html) -- 當下拉得到的新頁面與原來的相同,就認為它已經沒有新的內容了,此時就返回
    do
        old_html = html
  	    splash:runjs([[window.scrollTo(0, document.body.scrollHeight)]]) -- 執行js下拉頁面
		    splash:wait(math.random(1,2)) -- 這里一定要等待,否則可能會來不及加載,根據我的實驗只要大於1s就可以得到下拉加載的新內容,可能具體值需要根據不同的網絡環境

		    if (flush_times ~= 0 and i == flush_times) then -- 當達到設置下拉上限並且不為0時推出,這里下拉次數為0表示一直下拉直到沒有新內容
		        print("即將退出循環")
  	           break
  	        end

        html = splash:html()
  	    i = i + 1
    end

return {
    html = splash:html(),
    cookies = splash:get_cookies(),
}

上面的代碼中,首先請求時間線的界面,然后獲取相關的設置,主要是下拉次數。

注意這里的下拉次數要根據超時值來設置,根據splash的官方文檔,每個請求都有一個超時值,大於這個超時值會直接返回504 的錯誤這個時候就什么都得不到了,所以這里理想情況下是可以一直下拉的,但是由於有超時值的存在,必須給定一個下拉次數。我們可以在啟動splash 的時候通過參數--wait-timeout給定。
然后根據這個參數的設置一直下拉,直到沒有更新或者達到最大下來次數。

這個也有問題,如果網絡不好或者其他情況導致沒有加載出來,就認為它已經沒有新內容了,這樣會導致爬取內容不全

這樣我們就獲取到了用戶的時間線信息,具體的內容的解析就不再多說了,需要提醒一點的是,用戶發帖中包含圖片時分為三種情況,單個圖片,多個圖片(多個圖片一般就被叫做albums——相冊或者圖集),簡單的更新頭像;這三種情況下頁面的對應結構不同所以需要分情況,而且當時間線中包含視頻的時候情況又不同

獲取公共主頁的發帖信息

公共主頁中沒有時間線,所以它的解析與個人主頁的不同,好在Facebook提供了一種叫做圖譜API的東西可以很方便的就可以獲取到發帖信息。既然有這種API,為什么不用它獲取個人用戶信息呢?其實我也想用,就是要針對個人使用API就必須獲取用戶本人的確認,也就是要用戶登錄你的爬蟲,然后授權給你,這自然是不可能的,所以針對個人用戶只能簡單的通過模擬瀏覽器的方式來解析HTML頁面
要使用Facebook的 API首先要獲取一個access_token. 常規思路是先去去開發者平台注冊一個開發者賬號並建立一個應用。然后獲取應用的token。但是我發現一般的應用Token 在獲取公共主頁的時候也存在一個授權的問題,好在Facebook提供了一個api的測試平台,而平台中提供了一個graph explore token,這個token可以不用授權,但是它只有一個小時的有效期,所以要使用API,首先就是從這個測試平台獲取到這token。Facebook並沒有提供任何有效方法來獲取這個token,這個時候自然又要使用傳統的方式,通過splash請求這個url,然后解析HTML獲取對應token。
針對這個問題,我處理的步驟如下:

  1. 根據上一步獲取到的頁面類型,如果是公共頁面,則先請求https://developers.facebook.com/tools/explorer/這個url,在登錄狀態下(前提是你的對應賬號是Facebook的開發者賬號),它會自動生成一個測試用的access_token就像下面這樣
    API測試平台頁面
    輸入框中就是token
  2. 從該頁面中獲取到對應的token, 並調用對應的API獲取公共主頁的發帖信息,這里主要調用posts 並獲取它的鏈接、ID、具體信息、圖片、創建時間和編輯者 這些信息,具體的API文檔參考Facebook官方文檔,這里就不再介紹他們了
def get_access_token(self, response):
    sel = Selector(response=response)
    access_token = sel.xpath('//label[@class="_2toh _36wp _55r1 _58ak"]/input[@class="_58al"]//@value').extract_first() #獲取到token的值

    #拼接API
    api = urljoin("https://graph.facebook.com/v3.0", response.meta["user_id"])
    api = api + "/posts" + "?access_token=" + access_token + "&fields=link,id,message,full_picture,parent_id,created_time"

    yield Request(
        url=api,
        callback=self._get_public_posts,
        errback=self.error_parse
    )

API返回的信息是以json格式返回的,下面是使用posts返回的一個例子,這里只是作為一個例子,請求返回的內容與項目中可能並不一樣,但是並不影響針對它的分析

{
  "data": [
    {
      "created_time": "2018-06-01T22:30:00+0000",
      "message": "Congratulations to our contest winners, Joseph and Dana! It’s always a pleasure to meet the people who make our MOVEMENT possible. Thank you!",
      "id": "153080620724_10161074525735725"
    },
  ],
  "paging": {
    "cursors": {
      "before": "Q2c4U1pXNTBYM0YxWlhKNVgzTjBiM0o1WDJsa0R5QXhOVE13T0RBMk1qQTNNalE2T0RBeU9UTTROekExTURBek9UVTBNakkwTnc4TVlYQnBYM04wYjNKNVgybGtEeDR4TlRNd09EQTJNakEzTWpSZAk1UQXhOakV3TnpRMU1qVTNNelUzTWpVUEJIUnBiV1VHV3hISTZABRT0ZD",
      "after": "Q2c4U1pXNTBYM0YxWlhKNVgzTjBiM0o1WDJsa0R5QXhOVE13T0RBMk1qQTNNalE2T0RFMk9UZA3pOemN4TkRrMU1EWXpNVFEwTlE4TVlYQnBYM04wYjNKNVgybGtEeDR4TlRNd09EQTJNakEzTWpSZAk1UQXhOakV3TkRZANE1UazBNekEzTWpVUEJIUnBiV1VHV3dyV0FRRT0ZD"
    },
    "next": "https://graph.facebook.com/v3.0/153080620724/posts?access_token=EAACEdEose0cBAMo5MUmbhGNfJlCGcrZArh7RBiCeSbwpqUq84tyEOs3HvO5KWoOtkWERRgkZBFjvOCb1DQ3go7PywUrA43SdJTqjeyjs5p2w3UanCFm0jxiE4Dt91A4qcGQYo9iobrLVrtCL0bdoNdkicQb6izFzxZBx0sPVauofQMLZCEFNLNcFqfkAinPWtSVeUQRxjQZDZD&pretty=0&limit=25&after=Q2c4U1pXNTBYM0YxWlhKNVgzTjBiM0o1WDJsa0R5QXhOVE13T0RBMk1qQTNNalE2T0RFMk9UZA3pOemN4TkRrMU1EWXpNVFEwTlE4TVlYQnBYM04wYjNKNVgybGtEeDR4TlRNd09EQTJNakEzTWpSZAk1UQXhOakV3TkRZANE1UazBNekEzTWpVUEJIUnBiV1VHV3dyV0FRRT0ZD"
  }
}

這個json主要分為兩個部分一個是data,包含的是具體發帖的相關信息,另一個是paging,這個值里面包含了幾個游標,其中next表示下一頁的請求地址,我們只要判斷出json中存在這個next就循環向這個next對應的url發包,當返回的json中不存在這個next時就標明已經到了最后一頁。此時就解析了所有的發帖
下面是具體的代碼

def _get_public_posts(self, response):
        rep_json = json.loads(response.text)
        data = rep_json["data"]
        post_user = response.meta["user"]

        for post in data:
            try:
                item = FbspiderItem()
                item["post_id"] = post["id"]
                item["post_user"] = post_user
                item["post_message"] = post["message"]
                item["post_time"] = post["created_time"]
                item["post_link"] = post["link"]
                yield item

                #存儲圖片的信息
                item = FBPostImgItem()
                item["post_id"] = post["id"]
                item["img_url"] = post["full_picture"]
                yield  item

            except KeyError: # 暫時不處理未找到帖子內容的情況
                continue

        if "paging" not in rep_json:
            return

        paging = rep_json["paging"]
        if "next" in paging:
            api = paging["next"]
            yield Request(
                url = api,
                callback= self._get_public_posts,
                meta={"user" : post_user},
            )

獲取好友信息

獲取好友的信息也需要采用模擬瀏覽器的方式,首先在用戶頁面上查找是否有好友的鏈接可以供點擊,如果沒有說明沒有開放權限,當存在這個鏈接並點進去之后,可能並沒有好友項,比如下面這樣
不存在好友
或者另外的情況,所以這里判斷是否有好友信息需要兩步,第一步是上面部分有好友這一欄,第二步是點進去之后在下面一欄中有全部好友這項內容。
同樣即使有好友,它也不會一次加載完畢,這里也用到下拉的相關操作。部分代碼如下:

function main(splash, args)
    -- 設置cookie和代理
    local ok, reason = splash:go(args.url)
    splash:wait(math.random(5, 10))
    friend_btn = splash:select("a[data-tab-key= 'friends']") --查找最上面那欄中是否有好友這個鏈接
    if (friend_btn) then
        friend_btn:mouse_click({}) --點擊進入好友頁面
        splash:wait(math.random(5, 10))
    else
        return {
            hasFriend = false,
            cookie = splash:get_cookies(),
        }
    end

    return {
        hasFriend = true,
        html = splash:html(),
        cookie = splash:get_cookies(),
        url = splash:url(),
    }
end

執行完上述代碼后,再分析是否有對應的好友信息,有的話就下拉刷新頁面獲取更多好友信息

#當上面的代碼執行完后進入這個函數
def _get_friends_page(self, response):
        hasFriend = response.data["hasFriend"]
        if not hasFriend:
            print("用戶[%s]未開放好友查詢權限" % response.meta["name"])
            return

        html = response.data["html"]
        sel = Selector(response = response)
        friends = sel.xpath("//a[@name='全部好友']")
        if friends == []:
            print("用戶[%s]未開放好友查詢權限" % response.meta["name"])
            return
        #獲取代理,拼接對應的lua腳本
        yield SplashRequest(
            url = response.data["url"],
            callback= self.parse_friends,
            endpoint="execute",
            cookies= random.choice(self.cookie),
            meta= {"level" : response.meta["level"], "name" : response.meta["name"]},
            args={
                "wait" : 30,
                "lua_source" : lua_script,
            }
        )

當下拉結束之后就是解析頁面獲取頁面中的好友信息了,在解析的時候發現,當點擊某個好友進入它的主頁面時,頁面的鏈接為 https://www.facebook.com/profile.php?id=100011359746168&fref=pb&hc_location=friends_tab這個時候就會產生一種想法,這個id是不是就是用戶id呢?我用這個id來直接訪問用戶主頁行不行呢?經過我的實驗,這個想法是對的,是可行的,因此對於好友這層用戶我們可以直接拿到它的ID,不用向前面一樣根據名稱來拼接,下面是解析好友信息的部分代碼

def parse_friends(self, response):
        sel = Selector(response = response)
        friends = sel.xpath("//li[@class='_698']//div[@class='fsl fwb fcb']//a")

        #拼接lua腳本

        for friend in friends:
            url = friend.xpath(".//@href").extract_first()
            name = friend.xpath(".//text()").extract_first()

            #記錄當前好友關系
            friend_item = FBUserItem()
            friend_item["user_name"] = response.meta["name"]
            friend_item["friend_name"] = name

            print("提取到好友信息%s : %s" % (friend_item["user_name"], friend_item["friend_name"]))
            yield friend_item

            #這里再次提交請求主要是為了獲取好友的好友,以及獲取好友發的帖子
            #其實也可以在這個請求執行完成之后解析用戶主頁面得到用戶的ID等信息
            yield SplashRequest(
                url = url,
                endpoint="execute",
                callback= self.parse_main_page,
                meta={"name" : name, "level" : level},
                cookies = random.choice(self.cookie), # 從cookie池中隨機取出一個cookie
                args={
                    "wait": 30,
                    "lua_source": lua_script,
                }
            )

反爬蟲的相關操作

針對爬蟲程序來說最頭疼的就是有的站點在反爬蟲這塊做的太好了,Facebook就是這樣的一個站點,我的測試賬號在執行程序的時候被封過無數次。為了防止被封我主要采取了這樣幾個措施

減少並發數,設置發包的延時

這些內容主要在scrapy的配置文件中控制

DOWNLOAD_DELAY = 5 # 發包延時
CONCURRENT_REQUESTS = 16 # 最大並發數

設置代理池

代理池的設置通過下載中間件的process_request函數來設置,設置的相關代碼如下:

def process_request(self, request, spider):
    if self.proxies != []:
        proxy = random.choice(self.proxies) # self.proxies 是一個含有多個代理的列表,從中隨機取一個
        print("啟用代理:%s" % proxy)
        if "splash" in request.meta: #判斷是否是一個splash請求
            request.meta['splash']['args']['proxy'] = proxy# 設置splash代理
        else:
            request.meta["proxy"] = proxy #設置scrapy的代理

設置UA

在下載中間件的process_request函數中來設置,設置的方法與設置代理的方法類似

class RotateUserAgentMiddleware(UserAgentMiddleware):
    def __init__(self, user_agent=''):
        self.user_agent = user_agent

    def process_request(self, request, spider):
        ua = random.choice(self.user_agent_list)
        if ua:
            print("啟用UA :%s" % ua)
            request.headers.setdefault('User-Agent', ua)

    user_agent_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"
        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
    ]

設置多用戶登錄

這里我設置了多個登錄用戶,通過從用戶的登錄cookie池中隨機選取一個作為請求的cookie,在爬蟲開始位置導入多個用戶的用戶名和密碼信息,依次登錄,登錄成功后保存用戶cookie到列表,后面每次發包前隨機選取一個cookie

設置SplashReuqests函數的等待時間

就像前面代碼中每個SplashRequest函數的args參數中總會帶有 一個wait的鍵值,這個表示每次接到請求后等待的時長,加上這個是為了減慢爬蟲運行速度防止由於發包過快導致賬號被封

至此,我已將之前涉及到的所有問題基本上都提到了,很多地方我自認為處理的不是很完美,但是寫出來的內容勉強夠用。
這個爬蟲項目我最大的收獲就是知道了splash這個好用的東西,可惜的是它並沒有中文的文檔,所以像我這樣剛過四級的人讀起來還是有點吃力的。所以為了方便他人學習,以及提高我的英文水平我決定乘着這段時間我有空閑我想翻譯它的官方文檔。目前項目剛剛開始,地址為:splash中文文檔
PS: 不知道這個項目取這個名字會不會涉及到虛假宣傳或者版權什么的,如果涉及到這些我會立馬改名

最后的最后列舉出項目中參考的文檔,在整個項目中參考的文檔實在太多,不會一一列舉,這里只列舉我印象最深的一些

  1. 回歸爬蟲,擁抱scrapy&splash。抓facebook public post like、comment、share
  2. Splash官方文檔
  3. Scrapy文檔
  4. scrapy_splash項目文檔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM