202003裁判文書網採集思路 理論無視更新

!!以下思路從學習的角度出發,僅做學習交流使用,嚴禁將其應用在非法場景!!

初代機思路

約在 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()

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