Intro
對於使用JS動態加載, 或者將下一頁地址隱藏爲JavaScript void(0)
的網站, 如何爬取我們要的信息呢?
本文以Chrome
瀏覽器爲工具, 36Kr爲示例網站, 使用 Json Handle 作爲輔助信息解析工具, 演示如何抓取此類網站.
Detail
Step 1. 按下 F12 或右鍵檢查
進入開發者工具
Step 2. 選中Network一欄, 篩選XHR
請求
XHR
即 XMLHttpRequest
, 可以異步或同步返回服務器響應的請求, 並且能夠以文本或者一個 DOM 文檔的形式返回內容.
JSON是一種與XML在格式上很像, 但是佔用空間更小的數據交換格式, 全程是 JavaScript Object Notation, 本文中的36Kr動態加載時獲取到的信息就是JSON類型的數據.
網站爲了節省空間, 加快響應, 常常沒有對 JSON 進行格式化, 導致 JSON 的可讀性差, 難以尋找我們要的信息.
我們通過右鍵打開獲取到的 XHR 請求, 然後看看數據是怎樣的
未使用JSON Handle前
使用後
使用 Json Handle 後的數據可讀性就很高了
Step 3. 分析 URL
結合上面的截圖, 分析這條 URLhttps://36kr.com/api/newsflash?column_ids=69&no_bid=false&b_id=126035&per_page=20&_=1530699384159
這中間有兩個參數很容易可以知道它的用途, 第一個是per_page=20
, 第二個是_=1530699384159
第一個參數是我們每次滾動後可以獲取到的信息條數, 第二個是時間戳
試着改第一個參數改爲10, 可以看到條數就變爲10了.
修改per_page
改爲1000呢? 很遺憾, 最大值只有300. 換算下來, 就是最多允許爬 15 頁
滑動了超過15頁發現仍然有信息顯示, 經過轉換, 發現它的時間戳只是瀏覽網頁生成的時間戳, 與內容無關
按了幾個數字, 修改了b_id
的值, 發現內容確實發生了改變, 但b_id
又是網站設定的規則, 無從入手
每次獲取的最大值
改了no_bid
爲true
似乎沒有變化, 接着修改了column_id
爲70, 發現新聞的內容發生改變, 合理猜測這個應該是新聞標籤的id.
修改column_id
至此, 我們已大致瞭解整個 URL 的含義
per_page
每次滑動可以獲得的數據條目, 最大值爲300column_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 取值
取值方式簡單粗暴, 點擊對應的內容就可以看路徑了
JSON Handle查看路徑
接着用scrapy shell
工具測試下正確性, 然後就可以寫代碼了.
由於新聞來源隱藏在description
, 經過觀察, 不難發現它的規律, 寫一條正則獲取即可, 如果結果爲空, 則說明來源是36Krsrc_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
正在建設和維護中, 歡迎 star
和 issue
.