掘金15W沸點簡單分析(一)

> 數據分析之數據採集(僅Web爬蟲相關)。本文繼續從爬蟲入手。不過這次使用的是Python

一、另一種方式的爬蟲

爬蟲通常是:①得到目標網頁URL;②發起HTTP請求得到網頁數據;③使用各種方式解析網頁得到想要的數據;

通常,在第②步,是不會去執行頁面中的JS代碼的。有些網站,會使用Ajax異步加載一些數據,然後再渲染到頁面上的;或者使用JS對頁面Dom做一些修改。這樣都會導致第②步請求的頁面中缺少、甚至是沒有目標數據。這就需在獲取到網頁數據後,執行頁面中的JS代碼了。

最早是使用phantomjs+selenium。後面Chrome出了headless模式,基本就一直使用Chrome了。處理邏輯大概:①請求獲取網頁,執行JS代碼;②再將處理過的頁面數據進行保存;③後續的處理(解析網頁獲取數據)。

1.1 Selenium使用示例

我們就以掘金徵文爲例,獲取該文章下所有的評論。

注: 雖然可以直接通過接口獲取,但我們假設無法直接獲取數據,必須執行完JS才能得到目標數據。

使用Selenium + Chrome,首先需要下載與Chrome版本對應的ChromeDriver

示例代碼如下:

self.driver.get(self.article_url)
# 等待評論列表出現
WebDriverWait(self.driver, 10).until(
    EC.presence_of_element_located((By.CLASS_NAME, 'comment-list'))
)
self.save_page()

通過Selenium來操控Chrome加載網頁時,通常會遇到這種問題:網絡延遲,導致目標數據沒有及時下載下來,但此時已將網頁保存完畢。最簡單的方式是,每次發生加載網頁時,調用下time.sleep(5)類似的方式,但這種方式雖然簡單但卻比較粗暴。更好的辦法是使用Selenium提供的WebDriverWait來處理。

官方文檔,一定不要錯過:selenium-python

1.2 頁面的後續處理

將渲染後的網頁保存之後,接下來就要解析提取數據了。這次我們使用XPath來解析數據。

還是先分析下網頁

數據所處位置爲://div[@class="comment-list-box"]/div[contains(@class, "comment-list")]/div[@class="item"]

爲了處理方便,咱們僅獲取第一級的評論用戶和內容。

示例代碼如下:

root = etree.HTML(page_source)
comments = root.xpath('//div[@class="comment-list-box"]/div[contains(@class, "comment-list")]/div[@class="item"]')
for comment in comments:
    username = self.fix_content(comment.xpath('.//div[@class="meta-box"]//span[@class="name"]/text()'))
    content = self.fix_content(comment.xpath('.//div[@class="content"]//text()'))
    print(f'{username} --> {content}')

結果數據:

二、掘金沸點的獲取

咱們直接看沸點的API接口。

接口分析:

沸點接口地址:https://apinew.juejin.im/recommend_api/v1/short_msg/hot 請求方式:POST 請求參數類型及格式:JSON格式數據,

{
	cursor: "0",  // 遊標。 首次請求時爲"0",請求響應中會含有該字段。後續請求直接使用即可
    id_type: 4,  // 沸點類別??(無關緊要)
    limit: 20,  // 分頁大小 
    sort_type: 200  // 某種排序類型??(無關緊要)
}

然後我們就可以使用Python來模擬請求,獲取沸點數據了。

我們使用requests來模擬請求,具體使用請看官方文檔。

代碼示例:

HOT_URL = 'https://apinew.juejin.im/recommend_api/v1/short_msg/hot'
json_form = {
    'cursor': '0',
    'id_type': 4,
    'limit': 20,
    'sort_type': 200,
}
resp = requests.post(HOT_URL, json=json_form)
print(resp.json())

# 數據可以正常返回
# {'err_no': 0, 'err_msg': 'success', 'data': [{'msg_id': '6864704084000112654', 'msg_Info': {'id': 980761, 'msg_id': '6864704084000112654', 'user_id': '2207475080373639', 'topic_id': '6824710203301167112', 'content': '別去找女朋友了,你的女朋友來了',

2.1 將所有數據保存下來

根據上面的示例,我們優化下代碼:

def save_pins(idx=0, cursor='0'):
    json_data = {
        'id_type': 4,
        'sort_type': 200,
        'cursor': cursor,
        'limit': 200  # 該數值無限制,但過大服務器報錯,或者出現用戶信息缺失等情況
    }
    resp = sess.post(url, json=json_data)
    if resp.ok:
        resp_json = resp.json()
        with open(f'json_data/pins-{idx:04}.json', 'w+') as json_file:
            json_file.write(resp.content.decode('UTF-8'))
        # 是否還有更多
        if resp_json['err_no'] == 0 and resp_json['err_msg'] == 'success':
            logging.debug(f'no error, idx={idx}')
            if resp_json['has_more']:
                logging.debug(f'has more, next idx={idx+1}')
                time.sleep(5)
                save_pins(idx+1, cursor=resp_json['cursor'])
        else:
            # 出了異常
            logging.warning(resp_json['err_msg'])
            logging.debug(f'sleep 10s, retry idx={idx}')
            time.sleep(10)
            save_pins(idx, cursor)

部分數據如下:

源碼已上傳至GitHub, Gitee

三、後記

至此,整個熱點數據獲取已經結束。由於是直接請求接口返回的數據,除了部分數據重複外,幾乎不用做任何數據處理。

3.1 後續處理

計劃將數據簡單處理、存儲至MySQL數據庫,然後使用superset製作圖表。

3.2 針對掘金熱點的建議

  • 對分頁limit做限制,最大值限制爲:20?
  • 針對未登錄的情況,對可見數據量進行限制。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章