Bootstrap

爬虫新姿势——使用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();

}());

;