JS動態加載以及JavaScript void(0)的爬蟲解決方案

5530017-02bfb32d2b75825c.png

Intro


對於使用JS動態加載, 或者將下一頁地址隱藏爲JavaScript void(0)的網站, 如何爬取我們要的信息呢?

本文以Chrome瀏覽器爲工具, 36Kr爲示例網站, 使用 Json Handle 作爲輔助信息解析工具, 演示如何抓取此類網站.

Detail


Step 1. 按下 F12 或右鍵檢查進入開發者工具

Step 2. 選中Network一欄, 篩選XHR請求

XHRXMLHttpRequest, 可以異步或同步返回服務器響應的請求, 並且能夠以文本或者一個 DOM 文檔的形式返回內容.

JSON是一種與XML在格式上很像, 但是佔用空間更小的數據交換格式, 全程是 JavaScript Object Notation, 本文中的36Kr動態加載時獲取到的信息就是JSON類型的數據.

網站爲了節省空間, 加快響應, 常常沒有對 JSON 進行格式化, 導致 JSON 的可讀性差, 難以尋找我們要的信息.

我們通過右鍵打開獲取到的 XHR 請求, 然後看看數據是怎樣的

5530017-d3972a9ff86da4c8.png

5530017-b77b23b72429bc41.png

未使用JSON Handle前

5530017-a59cd479433bdcdd.png

使用後

使用 Json Handle 後的數據可讀性就很高了

Step 3. 分析 URL

結合上面的截圖, 分析這條 URL
https://36kr.com/api/newsflash?column_ids=69&no_bid=false&b_id=126035&per_page=20&_=1530699384159

這中間有兩個參數很容易可以知道它的用途, 第一個是per_page=20, 第二個是_=1530699384159
第一個參數是我們每次滾動後可以獲取到的信息條數, 第二個是時間戳

試着改第一個參數改爲10, 可以看到條數就變爲10了.

 

5530017-9786fc1a3f87e7e4.png

修改per_page

改爲1000呢? 很遺憾, 最大值只有300. 換算下來, 就是最多允許爬 15 頁

滑動了超過15頁發現仍然有信息顯示, 經過轉換, 發現它的時間戳只是瀏覽網頁生成的時間戳, 與內容無關
按了幾個數字, 修改了b_id的值, 發現內容確實發生了改變, b_id又是網站設定的規則, 無從入手

5530017-e607305fa60665e0.png

每次獲取的最大值

 

改了no_bidtrue似乎沒有變化, 接着修改了column_id爲70, 發現新聞的內容發生改變, 合理猜測這個應該是新聞標籤的id.

5530017-1d74f20e4294ebfe.png

修改column_id

 

至此, 我們已大致瞭解整個 URL 的含義

per_page 每次滑動可以獲得的數據條目, 最大值爲300
column_ids 新聞內容標籤, 69爲資本, 68爲B輪後等
b_id 新聞集合的某種id
時間戳 記錄當前的瀏覽時間

最後把原本的 URL 縮減爲
https://36kr.com/api/newsflash?column_ids=69&no_bid=true&b_id=&per_page=300

捨棄了b_id, 同時刪去時間戳, 防止服務器發現每次接收到的請求時間都是一樣的

經過測試, 上述的 URL 是可以獲取信息的

Step 4. 開始爬蟲

接下來的步驟與平時爬蟲類似.
不同的是獲取信息不再通過Xpath這些工具, 而是直接通過 JSON 取值

取值方式簡單粗暴, 點擊對應的內容就可以看路徑了

5530017-51ade887b0e5b6bd.png

JSON Handle查看路徑

接着用scrapy shell工具測試下正確性, 然後就可以寫代碼了.

由於新聞來源隱藏在description, 經過觀察, 不難發現它的規律, 寫一條正則獲取即可, 如果結果爲空, 則說明來源是36Kr
src_pattern = re.compile('。((.*))')

Source Code


Spider

# -*- coding: utf-8 -*-
import scrapy
import json
import re
from scrapy import Request
from ..items import FinvestItem


class A36krSpider(scrapy.Spider):
    name = '36kr'
    allowed_domains = ['36kr.com']
    start_urls = ['https://36kr.com/api/newsflash?column_ids=69&no_bid=true&b_id=&per_page=300']

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
    }

    def start_request(self):
        yield Request(self.start_urls, headers=self.headers)

    def parse(self, response):
        item = FinvestItem()
        # 轉化爲 unicode 編碼的數據
        sites = json.loads(response.body_as_unicode())

        src_pattern = re.compile('。((.*))')

        for i in sites['data']['items']:
            item['link'] = i['news_url']
            item['title'] = i['title']
            if src_pattern.search(i['description']) == None:
                item['source'] = "36Kr"
            else:
                item['source'] = src_pattern.search(i['description']).group(1)
            item['create_time'] = i['published_at']
            item['content'] = i['description']
            
            yield item

Pipeline

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import pymongo
import re
from scrapy.conf import settings


class FinvestPipeline(object):

    def __init__(self):
        """
        use for connecting to mongodb
        """
        # connect to db
        self.client = pymongo.MongoClient(host=settings['MONGO_HOST'], port=settings['MONGO_PORT'])
        # ADD if NEED account and password
        # self.client.admin.authenticate(host=settings['MONGO_USER'], settings['MONGO_PSW'])
        self.db = self.client[settings['MONGO_DB']]
        self.coll = self.db[settings['MONGO_COLL']]

    def process_item(self, item, spider):
        content = item['content']
        title = item['title']

        fin = re.compile(r'(?:p|P)re-?(?:A|B)輪|(?:A|B|C|D|E)+?1?2?3?輪|(?:天使輪|種子|首)輪|IPO|輪|(?:p|Pre)IPO')
        
        result = fin.findall(title)
        if(len(result) == 0):
            result = "未透露"
        else:
            result = ''.join(result)

        content = content.replace(u'<p>', u' ').replace(u'</p>', u' ').replace(u'\n\t', ' ').strip()
        # delete html label in content
        rule = re.compile(r'<[^>]+>', re.S)
        content = rule.sub('', content)


        item['content'] = content
        item['funding_round'] = result
        self.coll.insert(dict(item))
        return item

GitHub項目地址 finvest-spider

正在建設和維護中, 歡迎 starissue.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章