!!以下思路從學習的角度出發,僅做學習交流使用,嚴禁將其應用在非法場景!!
初代機思路
約在 2018年左右,文書網獲取數據的走的api是未加密的,那個時候採集難度最低,常規請求即可
二號機思路
2019年初文書網WEB端對接口訪問增加加密驗證,遂考慮對其APP進行逆向,分析出其加密方法及接口,請求之。
三號機思路
文書網現在web和app經過改版後,常規採集方式把握的死死的,再加上其甚至干擾正常訪問的驗證機制,使得常規採集方式愈發困難,目前網上大部分大神是對其加密方法進行逆向,雙方各顯神通,直趕軍備競賽,但是我們的目的是爲了獲取數據,所以我考慮了另外一種方式去實現功能,前後寫過python 和 node 版本,但基本思路是一致的,都是利用 puppeteer 或者 selenium控制 Choremium 進行採集,整個環節模擬人工操作,跳過了加密及反加密的驗證,該思路理論可採一切網站,對於有驗證碼的網站也可以加入判斷提示人工打碼,力求以最小的成本和較低的難度實現需求。
三號機β-nodejs
參照了一些基於node的採集項目,爲方便熟悉邏輯,我將註釋寫的很細。
'use strict'
// 引入 fs 模塊
const fs = require('fs');
// 引入 xlsx 模塊
const xlsx = require('node-xlsx').default;
// 引入核心 puppeteer
// npm install puppeteer --ignore-scripts
// 如果不加 --ignore-scripts 會報錯, 因爲它要下載 chormium, 國內網絡環境是搞不下來, 除非掛代理
const puppeteer = require('puppeteer');
(async () => {
// 設定 puppetter 啓動參數
// 主要是要配置 chrome.exe 的路徑 文章後邊會給下載鏈接
const browser = await puppeteer.launch({
// 把安裝後的 chrome 路徑填到下面
executablePath: './chrome-win/chrome.exe',
// 是否開啓無頭模式, 可以理解爲是否有可視的瀏覽器界面
headless: false, // 開啓界面
// defaultViewport: null,
// slowMo: 80,
});
// 新建一個網頁
const page = await browser.newPage();
// 跳轉至文書網
await page.goto('https://wenshu.court.gov.cn/');
// waitForSelector 等待 目標 渲染出來
await page.waitForSelector('#_view_1540966814000 > div > div.search-wrapper.clearfix > div.advenced-search');
// 模擬點擊高級檢索
await page.click('#_view_1540966814000 > div > div.search-wrapper.clearfix > div.advenced-search');
// 配置全文檢索的關鍵詞
const searchText = '安全';
// 全文檢索關鍵字
await page.type('#qbValue', searchText);
// 點擊全文檢索類型
await page.click('#qbType');
// 選擇理由
await page.click('#qwTypeUl > li:nth-child(6)');
// // 案件類型
await page.click('#selectCon_other_ajlx');
// 民事案件
await page.click('#gjjs_ajlx > li:nth-child(4)');
// 行政案件
await page.click('#gjjs_ajlx > li:nth-child(5)');
// 文書類型
await page.click('#_view_1540966814000 > div > div.advencedWrapper > div.inputWrapper.clearfix > div:nth-child(9) > div > div > div');
// 判決書
await page.click('#gjjs_wslx > li:nth-child(3)');
// 裁決書
await page.click('#gjjs_wslx > li.on');
// 年份開始(2017-01-01)
await page.type('#cprqStart', '2017-01-01');
// 年份結束(2020-12-31)
await page.type('#cprqEnd', '2020-12-31');
//當事人
await page.type('#s17', '');
// 點擊檢索
await page.click('#searchBtn');
await page.waitForSelector('#_view_1545184311000 > div.left_7_3 > div > select');
// 頁容量改爲15, 這樣從一個頁面採集的數量比較多
await page.select('#_view_1545184311000 > div.left_7_3 > div > select', '15');
// 等待 頁面內容刷出
await page.waitFor(500);
// 設置起始頁數
let pageNum = 1;
// 設置 excel 表頭
const data = [['DocID','案號', '標題', '案件類型', '當事人', '案由', 'pdf內容', 'html內容']];
let i = 1;
// while 裏面配置採集多少頁
while (pageNum < 2) {
pageNum++;
// 獲取頁面列表數據區域
const view = await page.$('#_view_1545184311000');
const lists = await view.$$('.LM_list');
// 循環數據列表
for (const list of lists) {
try {
// 獲取列表彙總每個信息的超鏈
const href = await list.$('div.list_title.clearfix > h4 > a');
// 獲取指向的地址
let href_url = await href.evaluate(node => node.href);
// 根據 href_url 獲取 docid, docID 即爲文書編號, 這裏使用正則
let docid = href_url.match(/docId=(\S*)/)[1];
// 獲取文書的案號
let ah = await list.$('div.list_subtitle > span.ah');
// 後邊會經常用到這個方法, innerText 用以獲取 字符串
ah = await ah.evaluate(node => node.innerText);
// 點擊詳情頁鏈接
await href.click();
// 等待加載
await page.waitFor(500);
// 第二個標籤頁的數據
const page2 = (await browser.pages())[2];
// xpath 獲取 title
let title = await page2.$('#_view_1541573883000 > div > div.PDF_box > div.PDF_title');
title = title !== null ? await title.evaluate(node => node.innerText) : '';
// 獲取 案件類型
let ajlx = await page2.$('#_view_1541573889000 > div:nth-child(1) > div.right_fixed > div.gaiyao_box > div.gaiyao_center > ul > li:nth-child(1) > h4:nth-child(2) > a');
ajlx = ajlx !== null ? await ajlx.evaluate(node => node.innerText) : '';
// 獲取 案件原因
let reason = await page2.$('#_view_1541573889000 > div:nth-child(1) > div.right_fixed > div.gaiyao_box > div.gaiyao_center > ul > li:nth-child(1) > h4:nth-child(3) > a');
reason = reason !== null ? await reason.evaluate(node => node.innerText) : '';
// 獲取 client
let client = await page2.$('#_view_1541573889000 > div:nth-child(1) > div.right_fixed > div.gaiyao_box > div.gaiyao_center > ul > li:nth-child(1) > h4:nth-child(6) > b');
client = client !== null ? await client.evaluate(node => node.innerText) : '';
// 獲取內容
let content = await page2.$('#_view_1541573883000 > div > div.PDF_box');
content = content !== null ? await content.evaluate(node => node.innerText) : '';
// 獲取 html , 可以根據這個去寫進一步邏輯 獲取內容的細分的字段
let html = await page2.$('#_view_1541573883000 > div > div.PDF_box');
html = html !== null ? await html.evaluate(node => node.innerHTML) : '';
// push 進數據池
data.push([docid, ah, title, ajlx, client, reason, content, html]);
console.log(`${i++}:${ah}`);
// 這個頁面的數據獲取後 關閉 這個標籤頁
await page2.close();
} catch (error) {
console.log(i++);
console.error('error:', error);
continue;
}
}
try {
// 當本頁面數據採集完後點擊分頁
await page.click(`#_view_1545184311000 > div.left_7_3 > a:nth-child(${pageNum + 1})`);
} catch (error) {
console.error('error:', error);
continue;
}
await page.waitFor(500);
}
// 整體採集完後關閉瀏覽器
await browser.close();
// 新建 xlsx 文件, 進行相應配置
const buffer = xlsx.build([
{
name: 'sheet1',
data,
}
]);
// fs 方法寫入內容
fs.writeFileSync('文書'+Date.now()+'.xlsx', buffer, { 'flag': 'w' });
})();
三號機α-python
以上 node 版本以可以完成任務,python 版本實現較早,很多代碼有過修改,但是基本思路和 node 版本一致,其中用了一個國內用的比較少的技術 ---- GraphQuery
GraphQuery —— 與任何後端服務相關聯的查詢語言和執行引擎 概述 GraphQuery 是一門易於使用的查詢語言,它內置了 Xpath/CSS/Regex/JSONpat
流程大約是 python 操作 selemium 控制 chormium 獲取頁面數據,將頁面數據傳給 GraphQuery,按照 GQ 規則寫好表達式,一起傳入 GQ 服務,其返回格式化的數據,以下代碼因版本和目標網站網站,部分代碼不是適配於文書網,僅做參考;
# 引入依賴庫, 需要視情況安裝
from selenium import webdriver
from threading import Thread
import pymysql
import requests
import json
import threading
import random
# 線程數量
tread_num = 10
# 採集完直接做寫入處理
def Insert(st_no='', title=''):
# SQL 插入語句
sql = "INSERT INTO chanpinhb(st_no, title ) VALUES ('%s', '%s')" % (st_no, title)
try:
# 執行sql語句
cursor.execute(sql)
# 執行sql語句
db.commit()
except:
# 發生錯誤時回滾
db.rollback()
# 打開數據庫連接
db = pymysql.connect("localhost", "root", "root", "demo")
# 使用 cursor() 方法創建一個遊標對象 cursor
cursor = db.cursor()
# 獲取chromedriver的位置
driver_path = r"G:\chromedriver_win32\chromedriver.exe"
opt = webdriver.ChromeOptions()
opt.add_argument('--headless')
opt.add_argument('--disable-gpu')
driver = webdriver.Chrome(executable_path=driver_path, options=opt)
# 傳入鏈接
driver.get(
"http://www.***.cn")
content = driver.page_source
# 獲取頁數//*[@id="b1"]/tbody/tr[15813]/td[3]/a
conseq = GraphQuery(content, r"""
{
url 'css(\"table a\")' [u 'href()']
}
""")
# 轉一下類型
count = json.loads(conseq)
count_int = count['data']['count']
count_int = int(count_int)
i = int(count_int)
def doit(content):
global i
global count_int
while i > 0:
if (i != count_int):
driver.find_element_by_id("ID_ucForceStandardList_UcPager1_btnNext").click()
content = driver.page_source
# 這塊是告訴 GQ 我們希望從傳入的數據中獲取的數據格式, GQ會根據規則返回格式化的數據
conseq = GraphQuery(content, r"""
{
title `css("[id*=\"ID_ucForceStandardList_dataListStandard_\"][id*=\"mainTd\"]")` [
st_no `regex("〖(.*?)〗")`
title `regex("〗(.*)</span>")`
]
}
""")
# 轉一下類型
Arr = json.loads(conseq)
# 遍歷出所需格式
source = Arr['data']['title']
length = len(source)
i_key = 0
a = []
while i_key < length:
a.append([source[i_key], source[i_key + 1]])
i_key = i_key + 2
for item in a:
Insert(item[0], item[1])
i = i - 1
# driver.close()
thread_list = []
for i in range(tread_num):
t = threading.Thread(target=doit(content))
t.start()
thread_list.append(t)
for t in thread_list: #等待所有線程執行完畢
t.join()
# 關閉數據庫連接
db.close()
# 關閉瀏覽器
driver.quit()