爬虫新姿势——使用Chrome Devtools写一个小说爬虫

目前,绝大部分的爬虫教程都是基于Python和Node.js。其实,只要有Chrome浏览器,使用Chrome F12打开的的Devtools就能随时随地轻轻松松写一个爬虫,完全不用装其它语言环境。今天就介绍一下只使用Chrome Devtools来爬取网站www.biqudu.com/31_31729/小说并保存为文本文件的爬虫。

如何在Chrome Devtools里面写爬虫代码

Devtools提供了Snippets功能让我们可以在这里写JavaScript代码,步骤参考下图:

步骤说明

  1. 打开source标签

  2. 左侧选择Snippets标签

  3. 点击New Snippets新建一个Snippets

  4. 开始写代码

  5. 点击运行代码

  6. 查看控制台输出


准备爬虫工具函数

1.加载第三方库

根据Url加载一个第三方库,可以用这个函数加载jquery,underscore等工具库,加载完成后就可以在代码中使用这些库了,本例中使用这个函数加载async异步并发控制库。

async function loadLibrary(url) {
    return new Promise((resolve, reject) => {
        let script = document.createElement('script');
        script.onload = resolve;
        script.onerror = reject;
        script.src = url;
        document.body.appendChild(script);
    });
}

2.下载文件到本地

将string下载到文本文件

function saveFile(string, fileName) {
    var a = document.createElement('a');
    a.download = fileName;
    var blob = new Blob([string], {
        type: 'text/plain'
    });
    a.href = window.URL.createObjectURL(blob);
    a.click();
}

3.下载HTML

使用了Fetch api,根据url下载一个html文本文件并转换成DOM元素后返回,返回的元素具有DOM api,例如 querySelector,方便对节点的提取和分析。

async function getHtml(url) {
    let response = await fetch(url);
    let htmlText = await response.text();
    let html = document.createElement('html');
    html.innerHTML = htmlText;
    return html;
}

准备爬虫业务函数

1.获取小说的所有章节信息

分析小说主页www.biqudu.com/31_31729/,

通过document.querySelectorAll('#list dd a') 可以获取包含所有章节名称和链接的a标签元素。

async function getDirectory(url) {
    let page = await getHtml(url);
    let directory = Array.from(page.querySelectorAll('#list dd a'));
    //去除顶部最新12个章节
    return directory.slice(12);
}

2.获取一个章节的内容

分析小说章节 www.biqudu.com/31_31729/21...,章节内容位于ID为content的DIV元素中

async function getSection({ href, innerText: title }) {
    console.log(`开始获取 ${title}`);
    let html = await getHtml(href);
    let content = html.querySelector('#content');
    Array.from(content.querySelectorAll('script')).forEach(scriptTag => content.removeChild(scriptTag));
    var text = title + '\r\n' + content.innerText + '\r\n';
    return text;
}

完整代码

因为小说有几百几千章节,不可能一个一个章节下载,那样速度太慢了。也不能一下子全下载。所以

爬取时使用了async异步并发控制库(这个async和async function里面的async只是名字一样而已),并发数量为6,设置大了也没用因为Chrome浏览器对同一域名下的同时请求数量是6。

完整代码运行步骤

  1. Chrome浏览器打开小说主页如:www.biqudu.com/31_31729/

  2. 在小说主页页面打开Devtools 新建snippets并将下面的完整代码粘贴进去

  3. 点击运行代码开始爬取小说

    (async function () {
    // https://www.biqudu.com/31_31729/
    async function loadLibrary(url) {
    return new Promise((resolve, reject) => {
    let script = document.createElement('script');
    script.onload = resolve;
    script.onerror = reject;
    script.src = url;
    document.body.appendChild(script);
    });
    }

     function saveFile(string, fileName) {
         var a = document.createElement('a');
         a.download = fileName;
         var blob = new Blob([string], {
             type: 'text/plain'
         });
         a.href = window.URL.createObjectURL(blob);
         a.click();
     }
    
     async function getHtml(url) {
         let response = await fetch(url);
         let htmlText = await response.text();
         let html = document.createElement('html');
         html.innerHTML = htmlText;
         return html;
     }
    
     async function getDirectory(url) {
         let page = await getHtml(url);
         let directory = Array.from(page.querySelectorAll('#list dd a'));
         //去除顶部最新12个章节
         return directory.slice(12);
     }
    
     async function getSection({ href, innerText: title }) {
         console.log(`开始获取 ${title}`);
         let html = await getHtml(href);
         let content = html.querySelector('#content');
         Array.from(content.querySelectorAll('script')).forEach(scriptTag => content.removeChild(scriptTag));
         var text = title + '\r\n' + content.innerText + '\r\n';
         return text;
     }
    
     async function run() {
         let asyncLibUrl = 'https://cdn.bootcss.com/async/2.1.4/async.js';
         await loadLibrary(asyncLibUrl);
         let directory = await getDirectory(location.href);
         let q = window.async.queue(async function (section, taskDone) {
             try {
                 section.text = await getSection(section);
             } catch (e) {
                 console.error(e);
                 section.text = "章节下载失败:" + e;
             } finally {
                 taskDone();
             }
         }, 6);//并发送设成6
    
         q.drain = function () {
             let name = document.querySelector('#maininfo h1').innerText + '.txt';
             console.log(`小说《${name}》下载完成`);
             let content = "";
             directory.forEach(function ({ text }) {
                 content += text;
             });
             saveFile(content, name);
         }
    
         q.push(directory);
     }
    
     await run();
    

    }());

相关推荐
tao_sc几秒前
luckfox-pico-max学习记录
学习
zx_zx_1232 分钟前
多态的学习
c++·学习
编码小袁2 分钟前
PHP:通往动态Web开发世界的桥梁
开发语言·前端·php
ch_s_t5 分钟前
基于flask+jwt+vue前后端分离架构
vue.js·python·flask
丁总学Java7 分钟前
npm list @types/node 命令用于列出当前项目中 @types/node 包及其依赖关系
前端·npm·node.js
初晴~2 小时前
【动态规划】打家劫舍类问题
java·数据结构·c++·python·算法·leetcode·动态规划
翔云API3 小时前
PHP开发示例-vin码识别接口-引领汽车行业数字化新风向
开发语言·php
ac-er88883 小时前
如何对PHP的API接口权限认证
开发语言·php
菜鸟、小高3 小时前
从0开始学PHP面向对象内容之(常用魔术方法续一)
开发语言·php
yezipi耶不耶3 小时前
Rust 所有权机制
开发语言·后端·rust