该程序采用Node中的puppeteer扩展组件,模拟人工操作,自动获取页面内容,并将获取的数据保存到excel中(每1000条保存一个文件,可以设置保存的条数)。excel文件内容如下图:

裁判文书网自动爬虫-最新更新2020-08-12

声明:本文章是以学习和交流为目的,数据源所有权归属原网站及所有者,严禁利用本文所提流程和数据进行盈利。

如果该代码对您有用,或者产生了帮忙,可以请我吃个冰棍或者来瓶可乐。感谢!

裁判文书网自动爬虫-最新更新2020-08-12

具体代码实现如下:

'use strict'

 

//软件名称:文书网自动爬虫 SuuperWenShuToExcel

//软件说明:本nodejs用于模拟浏览器爬取文书网的文书内容,并保存到excel中。

//作者:myhot 2020-07-01

//注意事项:

//  1、获取文书时,程序会自动打开chrome,运行过程中不能将chrome最小化,否则程序不能获取到文件内容而暂停

//  2、文书网中文书是定期进行发布,但获取文书是按裁判日期,因此每天发布的数据中裁判日期有1个月前的数据,因此需要获取1个月前的数据,才能保证完整。

//当前存在问题:

//  1、由于翻页时有可能不成功,重复获取当前页的文书,导致文书重复,加入重复判定,同时也会造成会丢失一页的文书数据

//使用协议:

//  1、您应当对使用该程序下载的数据具有管理负责,应用于正当目的;对于所产生的后果,本程序概不负责;

//  2、本程序已开源,您可以在此基础上进行任意修改和发布,但不可应用于商业目的。

//相关参考:

//  1、待鸣  https://blog.csdn.net/oZuoYu123/article/details/105225579

//版本历史:

//2020-08-02 v0.3

//  1、每1000个文件存一个新的文件前缀,因为达到2000个文书左右时,由于过大导致程序停止的bug

 

//2020-07-31 v0.2

//  1、增加法院、开始裁判日期等参数

//  2、每次获取1个月前的数据时,可先通过总数量进行判断,如果大于了本地数量,则再进行下载,否则认为本地是最新的,可不必依次下载,这样减少获取文书的时间

 

//2020-07-20 v1.1

//  1、实现指定检索条件,按日期依次爬取文书网内容,并存储到excel中,每日检索条件最多爬取10页

 

//以下为参数配置*************************************

//设置本次获取文书的裁判起始时间

var sdateTime=new Date('2019-06-21');

//设置本次获取的天数,默认180天

let nTday=365; 

//设置法院名称筛选条件

let sFyName='山东省淄博市中级人民法院';

//每多少条数据,存一个文件

let nSaveNums=1000;


 

//以下为代码正文*************************************

// 引入 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');

//序号

let iIndex = 1;

//每100保存一大份 ,起始为100,每次加100

let iThousandIndex = nSaveNums;

(async () => {

  // 设定 puppetter 启动参数

  // 主要是要配置 chrome.exe 的路径 文章后边会给下载链接

  const browser = await puppeteer.launch({

    // 把安装后的 chrome 路径填到当前js下面

    executablePath: './chrome-win/chrome.exe',

    // 是否开启无头模式, 可以理解为是否有可视的浏览器界面

    headless: false, // 开启界面

    defaultViewport: null, //全屏

    slowMo: 80,  //慢动作

    //ignoreDefaultArgs: ["--enable-automation"]//去除自动化测试的提醒  这个参数会使navigator.webdriver 为 True 文书网会判定为爬虫,不能请求返回数据

  });

  // 新建一个网页

  const page = await browser.newPage();

  //第一页加入反爬---navigator.webdriver 为 True 文书网会判定为爬虫,不能请求返回数据

  await page.evaluateOnNewDocument(() => {

    const newProto = navigator.__proto__;

    delete newProto.webdriver;

    navigator.__proto__ = newProto;

  });

  // 设置 excel 表头

  //裁判理由1  文书类型1 法律依据1 两个暂无

  const wenshuTitle = [['DocID','审判程序', '裁判法院', '裁判日期', '案号', '案件名称', '裁判理由', '案由','案件类型','文书类型','当事人','法律依据1','案件内容']];

  //初始化

  let data = [].concat(wenshuTitle);

  //设置起始天数

  let days = 0;

  while (days < nTday) {

    sdateTime=new Date(sdateTime);

    let starttime1= sdateTime.toISOString().substring(0,10);

    console.log("取该日期:" +starttime1);

    // 跳转至文书网

    try {

      await page.goto('https://wenshu.court.gov.cn/');

    } catch (error) {

      console.error('访问网页发生错误error:', error);

      console.error('重试访问');

      //退出本次循环,继续重试,还是在本页

      continue;

    }

    //未访问出该页面,则重试退出本次循环

    const teelement = await page.$('#_view_1540966814000');

    if (teelement==false)

    {

        //退出本次循环

        console.error('当前页面无法访问,重试访问');

        continue;

    }

    await page.waitFor(500);

    // waitForSelector 等待 目标 渲染出来

    await page.waitForSelector('#_view_1540966814000 > div > div.search-wrapper.clearfix > div.advenced-search');

    //等待

    //await page.waitFor(2000);

 

    // 模拟点击高级检索  输入条件

    await page.click('#_view_1540966814000 > div > div.search-wrapper.clearfix > div.advenced-search');

    // 配置全文检索的关键词

    // const searchText = '安全';

    // 全文检索关键字

    // await page.type('#qbValue', searchText);

    //法院名称

    if (sFyName!=''){

      await page.type('#s2', sFyName);

    }

    // 点击全文检索类型

    //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)');

    // 行政案件-myhot 注释

    //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)');

    // 裁决书--myhot 注释

    //await page.click('#gjjs_wslx > li.on');

    //年份开始(2017-01-01)    

    sdateTime=new Date(sdateTime);

    let starttime= sdateTime.toISOString().substring(0,10);

    //每次请求一天

    await page.type('#cprqStart', starttime);

    //年份结束(2020-12-31)

    await page.type('#cprqEnd', starttime);

    //当事人

    //await page.type('#s17', '');

 

    //点击检索

    await page.click('#searchBtn');

    //等待 页面内容刷出

    await page.waitFor(2000);

 

    //设置起始页数 从1开始必须的

    let pageNum = 1;

    let pageCount =0;

    //文书结果总数量

    let rTotalCount = 0;

    //更改分页15

    //myhot 注释 采用每页默认数量,不更改每页数量

    const element = await page.$('#_view_1545184311000 > div.left_7_3');

    if (element){   

      //await page.waitForSelector('#_view_1545184311000 > div.left_7_3 > div > select');

      // 页容量改为15, 这样从一个页面采集的数量比较多  ---myhot 注释

      //await page.select('#_view_1545184311000 > div.left_7_3 > div > select', '15');

      // 等待 页面内容刷出

      //await page.waitFor(1000);

      //搜索结果数量------------------------------------

      const viewtmp1 = await page.$('#_view_1545184311000 > div.LM_con.clearfix > div.fr.con_right > span');

      if (viewtmp1)

      {

        rTotalCount = await viewtmp1.evaluate(node => node.innerText) ;

      }

      console.log('文书结果总数:' +rTotalCount );      

    }

    //计算总页数  每页5条  采用上整除方法

    pageCount=  Math.ceil(rTotalCount / 5);

    if (pageCount>0)

    {

      console.log('搜索结果共' + pageCount +"页");

    }else//如果无分页,则说明该日期无文书记录,则直接下一轮日期

    {

        console.log('暂无分页数据');

    }

    // while 里面配置采集多少页 最多100页

    while (pageNum <= pageCount) {

      console.log('开始爬取第' +pageNum +'页');

      // 获取页面列表数据区域

      const view = await page.$('#_view_1545184311000');

      const lists = await view.$$('.LM_list');

      let href_url="";

      //重试标识

      let nRetry=0;

      // 循环数据列表

      for (const list of lists) {

        try {

          // 获取列表汇总每个信息的超链

          const href = await list.$('div.list_title.clearfix > h4 > a');

          // 获取指向的地址

          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);

          //裁判理由

          let cply = await list.$('div.list_reason > p');

          cply = cply !== null ? await cply.evaluate(node => node.innerText) : '';

          //2020-08-02 增加docid查重操作 由于翻页时可能不成功,重复获取当前页的文书,导致文书重复,加入重复判定,同时也会造成会丢失一页的文书数据

          for(let aa of data )

          {

              let bb =aa[0];            

              if (bb==docid)

              {

                console.log('docid重复,该文书丢弃,案号:'+ah);

                continue;

              }

          }

          // 点击详情页链接               

          // 新建一个网页

          const page2 = await browser.newPage();

          //第二页加入反爬---

          await page2.evaluateOnNewDocument(() => {

              const newProto = navigator.__proto__;

              delete newProto.webdriver;

              navigator.__proto__ = newProto;

          });

          // 跳转文书详细页 -myhot 

          try{

            //设置60秒超时

            await page2.setDefaultNavigationTimeout(0)

            await page2.goto(href_url);

          } catch (error) {

            console.log(iIndex++);

            console.error('跳转文书详细页:' + href_url);

            console.error('跳转文书详细页发生错误error:', error);

            //myhot 出现错误必须关闭本详细页,否则会一直停留在本页无法进行下一条数据 ----------

            if (page2!=null)

            {

              await page2.close();

            }

            continue;

          }

          //等待加载

          await page2.waitFor(5000);

          //判断是否为空页

          //未访问出该页面,则重试退出本次循环

          const teelement = await page2.$('#_view_1541573883000');

          if (teelement==false)

          {

              //退出本次循环

              console.error(href_url + '当前文书页面无内容,进入下一文件循环');

              //必须关闭本详细页,否则无法向下一条文书进行

              await page2.close();

              continue;

          }

          // xpath 获取标题

          let title = await page2.$('#_view_1541573883000 > div > div.PDF_box > div.PDF_title');

          title = title !== null ? await title.evaluate(node => node.innerText) : '';

          title=title.trim();

          //文书类型

          let wslx = await page2.$('#_view_1541573883000 > div > div.PDF_box > div.PDF_pox > div:nth-child(3)');

          wslx = wslx !== null ? await wslx.evaluate(node => node.innerText) : '';

          //去除所空格

          wslx=wslx.trim().replace(/\s+/g,"");

          // 获取 审理法院

          let slfy = 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(1) > a');

          slfy = slfy !== null ? await slfy.evaluate(node => node.innerText) : '';

          slfy=slfy.trim();

          // 获取 案件类型

          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) : '';

          ajlx=ajlx.trim();

          // 获取 当事人

          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) : '';

          client=client.trim();

          //有一些案件为不公开案件,因此赋值为标题,其它资料获取也进行相应变化

          let reason="";let spcx="";let cjrq="";

          if (client!="")

          {

            // 获取 案由

            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) : '';

            reason=reason.trim();

             //审判程序1

            spcx = 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(4) > b');

            spcx = spcx !== null ? await spcx.evaluate(node => node.innerText) : '';

            spcx=spcx.trim();

            //裁决日期

            cjrq = 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(5) > b');

            cjrq = cjrq !== null ? await cjrq.evaluate(node => node.innerText) : '';

            cjrq=cjrq.trim();

          }

          else

          {

            //当前事赋值为标准

            client=title + "-不公开案件";

            // 获取 案由

            reason ="不公开案件";

             //审判程序1

            spcx = 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) > b');

            spcx = spcx !== null ? await spcx.evaluate(node => node.innerText) : '';

            spcx=spcx.trim();

            //裁决日期

            cjrq = 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(4) > b');

            cjrq = cjrq !== null ? await cjrq.evaluate(node => node.innerText) : '';

            cjrq=cjrq.trim();

          }          

          // 获取内容

          let content = await page2.$('#_view_1541573883000 > div > div.PDF_box');

          content = content !== null ? await content.evaluate(node => node.innerText) : '';

          content=content.trim();

          // 获取 html , 可以根据这个去写进一步逻辑 获取内容的细分的字段

          let html = await page2.$('#_view_1541573883000 > div > div.PDF_box');

          html = html !== null ? await html.evaluate(node => node.innerHTML) : '';

          //如果为空,则不进行写入

          if(spcx=='undefined' || spcx=='')

          {

            //如果该文书未重试过,则允许重试一次

            if (nRetry==0)

            {

              console.error('发生错误:该文书审理程序为空,重试');

              nRetry=1;//赋值为重试标识

              if (page2!=null)

              {

                await page2.close();

              }

              continue;

            }else{

              nRetry=0; //如果重试过,审核程序还是未空,则略过此文书

              console.error('发生错误:该文件审理程序为空,已重试失败,略过此文书');

            }            

          }

          else

          {

            nRetry=0;

            //push 进文书数据池

            data.push([docid, spcx ,slfy ,cjrq,ah,title,cply,reason,ajlx,wslx,client,'',content]);

          }          

          //输出,因有时区问题转换为中国东八区

          const curDate = new Date(+new Date() + 8 * 3600 * 1000);

          console.log(curDate.toISOString().substring(0,19).replace('T',' ')+ ' '+ `${iIndex++}:${ah}`);

          // 这个页面的数据获取后 关闭 这个标签页  

          //myhot 正常关闭本页 ----------

          await page2.close();

        } catch (error) {

          console.log(iIndex++);

          console.error('发生错误url:' + href_url);

          console.error('error:', error);

          //myhot 出现错误必须关闭本详细页,否则会一直停留在本页无法进行下一条数据 ----------

          if (page2!=null)

          {

            await page2.close();

          }

          continue;

        }

      }

      //下一页开始

      try {

        // 当本页面数据采集完后点击分页  如果有分页

        const element = await page.$('#_view_1545184311000 > div.left_7_3');

        if (element){

          //下一页 从第1页是从第2个元素开始的,因此需要加1

          pageNum++;

          //如果大于了总页数,则已到末尾

          if (pageNum<=pageCount)

          {

            //寻找下一页按钮,为最后一个节点,进行点击

            const viewtmp = await page.$('#_view_1545184311000');

            const listsPages = await viewtmp.$$('.left_7_3 > a');

            if (listsPages)

            {

              let curpageNum=listsPages.length;

              //点击第下一页

              await page.waitForSelector('#_view_1545184311000 > div.left_7_3 > a:nth-child(' + curpageNum +')'); 

              await page.click('#_view_1545184311000 > div.left_7_3 > a:nth-child(' + curpageNum +')'); 

              //下面这种有时会第2页点击无反应,因此注释采用上一种方法

              //await page.click(`#_view_1545184311000 > div.left_7_3 > a:nth-child(${curpageNum })`);

              await page.waitFor(2000);

            }else

            //末页

            {

              console.log('未找到分页信息,当前第' +pageNum  +'页');

            }

          }else

          {

            console.log('末页');

          }

          

        }

      } catch (error) {

        console.error('下一页访问失败error:', error);

        continue;

      }

      //每2个写一次

      if (pageNum%2==0)

      {

        console.log('写入fs');

        // 新建 xlsx 文件, 进行相应配置

        const buffer = xlsx.build([

            {

            name: 'sheet1',

            data,//必须用data,不能使用其它变量名,否则为空

            }

        ]);

        //每当大于1000的时候,就保存另外一个文件名,并清空data

        if (iIndex >= iThousandIndex)

        {

            fs.writeFileSync(iIndex +'-'+ Date.now() +'文书.xlsx', buffer, { 'flag': 'w' });

            iThousandIndex=iThousandIndex + nSaveNums;

            //重新赋值

            data = [].concat(wenshuTitle);

        }

        //fs.writeFileSync('文书'+Date.now()+'.xlsx', buffer, { 'flag': 'w' });

      }

    }

    //加1天 取下一个日期

    sdateTime=sdateTime.setDate(sdateTime.getDate()+1);

    days++;

  }

  // 整体采集完后关闭浏览器

  await browser.close();

  // 新建 xlsx 文件, 进行相应配置

  const buffer = xlsx.build([

    {

      name: 'sheet1',

      data,

    }

  ]);

  // fs 方法写入内容

  fs.writeFileSync(iIndex +'-'+ Date.now() +'文书.xlsx', buffer, { 'flag': 'w' });

  console.log('整体数据采集完');

})();

 

QQ与微信同号:115392406

有啥情况可以联系!

相关文章: