1. 程式人生 > >爬蟲之刃----趕集網招聘類爬取案例詳解(系列四)

爬蟲之刃----趕集網招聘類爬取案例詳解(系列四)

前言


本篇承襲之前的系列文章,開始動真格。以趕集網招聘類資訊爬取為例,詳細解說爬蟲程式構建過程。

準備工作:

  1. 閱讀之前的系列一、系列二、系列三,有一定遞進關係
  2. 登陸趕集網,瞭解下“地形”

OK,let’s go!


構建URL庫


每個網站的URL都會有一定規律,或強或弱。趕集網的URL規律就非常明顯。

鄭重說明:對於目標站點,必須熟悉。這一步對於不同的網站有不同的方法。而趕集的話,是我通過仔細觀察得到url規律。以趕集招聘類目舉例,如下:

  1. http://www.ganji.com/index.htm 頁面包含了所有的城市,每個城市有固定的簡稱,如:上海是sh,北京是bj
  2. 每個城市的招聘類目網址均帶有zhaoping,如:上海是http://sh.ganji.com/zhaopin/,北京是http://bj.ganji.com/zhaopin/
  3. 每個城市的職業分類均是相同的,具體的職業分類標識均是相同的,如:電話銷售是zpdianhuaxiaoshou,售前工程師是zpsqgongchengshi。相應的該類目下的連結是(已北京為例):http://bj.ganji.com/zpdianhuaxiaoshou/http://bj.ganji.com/zpsqgongchengshi/
  4. 有了city+category的URL以後,可以在URL後追加頁數,如北京的電話銷售第一頁:
    http://bj.ganji.com/zpdianhuaxiaoshou/o1/
    ,第二頁:http://bj.ganji.com/zpdianhuaxiaoshou/o2/

URL規律就說到這裡。順便一提,趕集網的3G手機版的也是相似的規律。

說說幾個誤區:

  1. 擔心漏爬。因為分類資訊網站的分類還有很多,剛才我分析的太簡單了?
    谷震平 blog
    負責的告訴大家,以上的分類完全不用管,不必去構造區域(海淀、朝陽、東城等)、月薪、福利、其他的URL規律。例如只要選了電話銷售,這些資訊均包含在此連結下。更多的篩選條件只是決定了哪些資訊可以優先顯示罷了,對於全部需要(如果你不需要全部,當我沒說)爬取的爬蟲是沒有意義的。

  2. 所有的資料都應該遵循自動獲取(除了第一個url是手動輸入),而非人工錄入。比如,城市類目。雖然,城市類目一般不會變,但是如果趕集增加和放棄某個/些城市了,爬蟲就完了。

  3. 沒有固定的和絕對的規則/規律,一定要保持爬蟲的靈活。不要以為總結了很多趕集的規律,就算結束了。實時盯著自己的爬蟲。比如,城市類目裡:
    谷震平 blog
    最後一行,居然有釣魚島!我只能告訴你,這個城市連結下的資料和其他所有城市都不一樣。你的爬蟲裡的正則、xpath、爬取方式在釣魚島下均不適用。但是,你一定要知道:釣魚島是中國的!


去重模組


去重去的好,可以節省很多的時間。但是在不同的應用場景裡是有很多差異之處。切記套模板,必須自己去思考。

在趕集網的爬取過程中,因為需求是:爬到更多的新資料。而這樣必然產生大量的資料。所以,我沒有采用每個具體頁面的id去重(去重速度慢。一般的應用場景應當考慮利用id),而是判斷listing頁面(一般包含30個具體頁面的摘要資訊)是否已爬過。

所以,構建了一個去重的資料庫。表結構:
谷震平 blog

把每次爬到的URL都放在mongo裡,設定如下幾個欄位:

  • _id:等同於cityCategoryPageUrl欄位
  • cityCategoryPageUrl: 城市+類目+頁數的URL
  • cityCategoryUrl: 城市+類目的URL
  • status: 該cityCategoryPageUrl是否已爬過。0:未爬過,1:已爬
  • endPage: 該cityCategoryUrl是否已爬完。0:為爬完,1:已爬
  • crawlTime:插入資料庫的時間
  • crawlDay: 插入資料庫的日期

OK,寫到這裡,宣告下:我是按cityCategoryPageUrl這個欄位的值來爬資料的。這個欄位的狀態是0就去爬取該頁面,否則放棄。

那endPage是幹什麼用的?
因為每次中斷後,就不知道該是從那個頁面開始了。不想每個頁面都檢查一遍是否是已爬過(status == 1)狀態。所以先檢查cityCategoryUrl是否是已爬過。

梳理一下:
谷震平 blog專用圖片 版權所有,侵權必究

最後,再次強調:去重模組的資料表中,沒有具體帖子的URL狀態。因為,資料太多了,每次都檢查不過來。而且帖子都在新增和刪除(使用者行為),是變動的資料。


爬取模組


既然URL已經構建好了,爬取就變得簡單了。利用requests模組庫傳送HTTP請求,得到網站原始碼,利用lxml模組庫進行DOM化,結合xpath爬取即可。中間的個別技術問題,一樣網路均有答案,不再贅述。

給出一份Python版虛擬碼,僅供參考:

def proxy():
    """購買代理IP"""
    proxies = 購買代理IP
    return proxies

def check_fanpa(response):
    """檢測反爬蟲頁面"""
    title = 檢測網頁內容,如判斷title內容
    if "404" in title:
        check_result = False
    elif "502" in title:
        check_result = False
    elif "反爬取" in title:
        check_result = False
    else:
        check_result = True
    return check_result

def request(url):
    """傳送HTTP請求,獲得原始碼"""
    hea={常用的http頭}
    proxies = proxy()   # 呼叫,獲取代理IP
    response = requests.get(url,proxies=proxies,headers=hea,timeout=1)

    check_fanpa_result = check_fanpa(response)    # 呼叫,獲取反爬蟲檢測結果
    if check_fanpa_result is False:
        再次嘗試http請求,這裡可以有一個請求限制,最多/少10else:
        return response    # 返回正常結果
    return None  # 超出了請求限制的特殊情況

def check_quchong(url):
    """url去重"""
    quchong_result = 在已爬過的資料中尋找是否有相同的url
    if quchong_result is not None:
        return False   # 重複的URL
    else:
        return True    # 沒有該URL

def parse(response):
    xpath_map = {
        "title_xpath": "//html/header/title/text()" ,
        "...": "...",
        "...": "...",
    }
    tree = lxml.etree.HTML(response.text)
    title_data = tree.xpath(xpath_map['title_xpath'])

    data = {}    

    data['title'] = title_data[0]

    return data    # 返回所用資料

def storage(data):
    """資料儲存,細節過程就不說了"""
    將data放在mongo中
    return True

def crawl():
    """爬取模組"""
    url = 從構建的url庫中獲取
    check_quchong_result = check_quchong(url)   # 呼叫,檢測是否已經爬過
    if check_quchong_result is True:
        return None 
    else:
        response = request(url)    # 呼叫,獲得原始碼/None
        if response is None:
            return None  # 因為是異常,返回None,當然也可用pass讓程式忽略繼續向下走

        parse_data = parse(response)  # 呼叫,獲得解析好的data
        storage_res = storage(parse_data)  # 呼叫,儲存資料

實際上,以上程式碼只是展示了基本的爬蟲邏輯。實際應用中,完全不能這麼寫。上面的虛擬碼連最基本的非同步都沒有做的,所以就看看邏輯就好。一般的爬蟲,和上面的爬取邏輯並無二致。


代理模組


在上述爬取過程中,大量快速的訪問,將觸犯趕集的反爬取機制,HTTP請求後的response都是不包含可用資訊的原始碼。這些頁面的種類有很多,如:反爬頁面,404頁面,403頁面,502頁面等等。我在自己的github上共享過相關的資料,僅供參考,請戳傳送門—-趕集反爬。後期,會有一篇文章展開講反爬的故事。

面對眾多的不可用頁面的情況,最好的辦法就是使用代理IP。在使用代理的過程中,也必須驗證返回的原始碼是否是自己真正需要的,不是,就必須換一個代理IP,重新去請求。有些場景,需要不斷換代理IP,直到獲得真正的頁面原始碼;有些場景,設定請求次數,如10次,換10個不同的代理IP之後,如果拿到的還不是自己需要的原始碼,就放棄該頁面。


儲存模組


要存的資料大致有兩種:

  • 臨時儲存的資料
  • 永久儲存的資料

臨時儲存的資料,放在redis中;永久儲存的資料,放在mongo中。根據應用場景,靈活的配合使用。

redis可以存放http請求後的原始碼。將網頁原始碼序列化後,存放在redis佇列中,再利用其它程序,如解析程序,進行解析。得到的資料如果是永久資料,可以直接存放到mongo中。如果還有需求方需要,可以再次放在redis中,讓該需求方去拿即可。

構建非同步程式過程中,使用redis有兩種基本的方法:

  • 生產者–消費者模式
  • 釋出者–訂閱者模式

兩者區別較大,設計redis佇列需要考慮。相關資料參考博文:【Redis—-非同步程式之訊息佇列的應用與實踐】。

mongo用於存放永久資料,比較簡單方便。保證插入時不出錯,是爬蟲程式中最常見的需求。可以在插入資料出錯後,檢測一下是否有相同的key。視情況,放棄該資料,或者更新該資料。


結語


寫的越多,發現想說的越多。其中細節,怎麼說也說不完。所以,任何問題,歡迎留言交流。^_^

文章同步更新在簡書、稀土掘金、知乎、CSDN部落格、微信公眾號上,個人原創,尊重版權,轉載請宣告!

更多精彩內容,可以關注微信公眾賬號【谷震平的專欄】獲取。
谷震平 爬蟲 blog

這裡寫圖片描述