chrome 开发模版学习提升

【编程】Chrome extension扩展开发实战

2020-04-06  本文已影响0人  zhyuzh3d

怎样从众多网页上快速提取信息?比如说:

你可能需要kSpider这样一个工具。我的开源项目地址 http://10knet.com/zhyuzh/kspider

强烈建议你仔细阅读下面的内容然后再使用。

问题分析

由于现代Web开发方式(React,Vue,AngularJS等)的流行,很多页面都采用先加载页面再用js加载数据的模式,这就导致我们在页面上看到的内容和网页源码不一致,简单说就是Ctrl+S保存下来的页面文件并不包含你想要的信息。——这对于数据采集来说非常不利。

当然,可以使用爬虫技术直接从页面的Request接口中直接获取数据,但很多网站越来越多的使用反爬虫机制来阻止这种方式,爬虫技术和反爬虫技术之争就是魔道之争,总体来说,爬虫技术是很被动的,人家服务端换个花样,就让爬虫工程师折腾几天。

解决思路

对于普通用户来说,终极解决方案是浏览器自动化,模拟人类的浏览行为进行数据获取。换句话说,就是人能看到的数据就能够抓取下来。这样才能让爬虫工程师以不变应反爬虫服务器的万变。

PhantomJS和Selenium等爬虫工具都是类似的思路。但这些编程框架对于普通办公人员来说都太难使用。其实我们日常最多的工作就是抓取数十数百个数据,没必要兴师动众的写程序。用几个小时写爬虫,测爬虫,好容易代码成功运行,又用不了几天就被服务器升级搞得不能用...真不如每天花十几分钟手工保存来的简单,反正每天的新数据也就几十几百条而已。

最直接的简单思路就是:

  1. 手工打开这些页面(能脚本自动化打开更好);
  2. 执行一些必要的点击操作(或者根本不需要操作);
  3. 把看到的页面内容保存下来(注意是页面内容而不是页面源码);
  4. 用Python读取这些页面,BeautifulSoup解析,提取需要的信息,做数据处理。

解决方案

恐怕没有什么比直接写个Chrome浏览器插件来的更简单。写一个插件,实现保存单个页面内容,批量保存多个页面内容的方法,将会是个非常好的起步。

下面先从几个步骤简单介绍这个kSpider插件的开发思路。

插件本质

Chrome插件就是一个文件夹里面的几个web网页和js代码。和常规网页不同的是这些js代码可以使用Chrome的各种接口,简单说就是可以用js控制Chrome浏览器甚至进一步控制操作系统。比如说用js来打开和关闭tab页面,甚至捕获操作系统桌面。

调试插件

怎么运行下载的项目或者自己编写的项目?

从右上角菜单【更多工具-扩展程序】打开页面。

勾选右上角的【开发者模式】,然后就可以加载你的项目文件夹,修改后只要点这个刷新按钮即可重新加载运行。

manifest.json

项目文件夹必须要有一个manifest.json文件,它设置了使用哪些网页和js代码文件。官方说明看这里:https://developer.chrome.com/extensions/manifest

下面这个是我的配置,仅供参考。

{
  "name": "kSpider",
  "version": "1.0",
  "description": "Data spider!",
  "manifest_version": 2,
  "icons": {
    "16": "public/img/icon.png",
    "48": "public/img/icon.png",
    "128": "public/img/icon.png"
  },
  "background": {
    "page": "public/index.html",
    "_scripts": [
      "public/js/background.js"
    ],
    "persistent": true
  },
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": [
        "public/js/docstart.js"
      ],
      "run_at": "document_start"
    },
    {
      "matches": [
        "<all_urls>"
      ],
      "js": [
        "public/js/docend.js"
      ],
      "run_at": "document_end"
    }
  ],
  "permissions": [
    "activeTab",
    "activeTab",
    "background",
    "downloads",
    "declarativeContent",
    "history",
    "notifications",
    "pageCapture",
    "tabCapture",
    "unlimitedStorage",
    "storage",
    "webNavigation",
    "webRequest",
    "<all_urls>",
    "alarms"
  ],
  "page_action": {
    "default_popup": "public/popup.html",
    "default_icon": {
      "16": "public/img/icon.png",
      "48": "public/img/icon.png",
      "128": "public/img/icon.png"
    }
  },
  "options_page": "public/options.html",
  "commands": {
    "saveThisPage": {
      "suggested_key": {
        "default": "Ctrl+Shift+S",
        "mac": "Command+Shift+S"
      },
      "description": "快速保存当前页面内容"
    }
  }
}

需要特别注意的是下面几点:

chrome.windows.create({
    url: chrome.extension.getURL("public/index.html"),
    type: "popup",
    height: 480,
  });
chrome.commands.onCommand.addListener(function (cmd) {
    if (cmd == 'saveThisPage') { //ctrl+shif+s 保存当前页面
        try {
            saveThisPage()
        } catch (e) {
            console.log('kSpider:saveThisPage failed:', e)
        }
    }
})

代码实现

下图是我的项目文件目录。


其中真正有用的就是index.html-index.js、popup.html-popup.js、docend.js,其他的都可以忽略。
下面是这几个文件的代码,最核心的部分都在index.js里面,这里并没有用到网上提到较多的message通信方法,而是使用更巧妙的办法实现了页面内容的获取和存储,你可能需要仔细看一下代码注释,这里不啰嗦解释了。

index.js

console.log('kSpider:Hello from kspiders index.js.')

var urlList = [] //所有待处理的地址列表
var pageCode = 'console.log("kSpider:Runcode for ervery page.")' //每个页面要执行的代码

//确保扩展能够对每个页面都有效
chrome.runtime.onInstalled.addListener(function () {
    //初始化后背景页面控制台输出
    chrome.storage.sync.set({ color: '#3aa757' }, function () {
        console.log('Hello from kspiders onInstalled background.js.');
    });

    //所有页面都激活扩展
    chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
        chrome.declarativeContent.onPageChanged.addRules([{
            conditions: [new chrome.declarativeContent.PageStateMatcher({
                pageUrl: { hostContains: '' },
            })],
            actions: [new chrome.declarativeContent.ShowPageAction()]
        }]);
    });
});


let docHtmlStr = 'document.documentElement.innerHTML' //获取当前页面内容的命令
let curActTabInfo = { active: true, currentWindow: true, url: '<all_urls>' } //过滤当前激活窗口的设置

/**
 * 保存页面特定内容到html
 * 为index.js中的pageRun函数的querystr提供支援
 * 存储的文件名,kSpiderSavedPage.html,路径使用用户默认设置,自动避免重名
 * 不同于保存源代码,这是将整个DOM实时的内容进行保存,需要关闭浏览器【设置-高级-下载内容-下之前询问...】
 * @param {*} content   字符串,文字内容
 */
function saveContent(content) {
    if (!content) content = document.documentElement.innerHTML
    let blob = new Blob([content], { type: 'text/html' });
    let objectURL = URL.createObjectURL(blob);
    let filename = 'kSpiderSavedPage.html'
    let conf = { url: objectURL, filename: filename, conflictAction: "uniquify" }
    chrome.downloads.download(conf, function (downloadId) {
        console.log("kSpider:SavePage filename:", filename, downloadId);
    });
}

/**
 * 在页面上执行动作,可以是tabsinfo过滤到的多个页面
 * 只能根据tabinfo对象进行筛选页面,不能直接指定页面
 * @param {*} [callback=(r) => { console.log(r) }]  要执行的函数,r参数是querystr返回的结果,比如一个页面元素
 * @param {*} querystr  字符串,可以使用JQuery语法,$('.someclass').click()
 * @param {boolean} [tabsinfo={ active: true, currentWindow: true }]    选择目标页面,默认是当前页
 */
function pageRun(callback = (r) => { console.log(r) }, querystr = docHtmlStr, tabsinfo = curActTabInfo) {
    chrome.tabs.query(tabsinfo, function (tabs) {
        for (var i = 0; i < tabs.length; i++) {
            console.log(tabs[i].url)
            chrome.tabs.executeScript(tabs[i].id, { code: querystr }, function (result) {
                callback(result)
            })
        }
    })
}

/**
 * 保存当前激活的页面内容
 */
function saveThisPage() {
    pageRun(saveContent, 'document.documentElement.innerHTML', { active: true, currentWindow: true, url: '<all_urls>' })
}

/**
 * 保存当前窗口所有页面内容
 */
function saveAllPage() {
    pageRun(saveContent, 'document.documentElement.innerHTML', { currentWindow: true, url: ['http://*/*', 'https://*/*'] })
}

//快捷键监听
chrome.commands.onCommand.addListener(function (cmd) {
    if (cmd == 'saveThisPage') { //ctrl+shif+s 保存当前页面
        try {
            saveThisPage()
        } catch (e) {
            console.log('kSpider:saveThisPage failed:', e)
        }
    }
})

popup.js

console.log('kSpider:Hello from popup.js.')

let kbg = chrome.extension.getBackgroundPage() //调用index的方法

//一键保存当前页面到预先设定的路径
let saveThisBtn = document.getElementById('saveThis');
saveThisBtn.onclick = (e) => {
  try {
    kbg.saveThisPage()
  } catch (e) {
    console.log('kSpider:saveThisPage failed:', e)
  }
}

//一键保存所有页面到预先设定的路径
let saveAllBtn = document.getElementById('saveAll');
saveAllBtn.onclick = (e) => {
  try {
    kbg.saveAllPage()
  } catch (e) {
    console.log('kSpider:saveAllPage failed:', e)
  }
}

//打开kSpider控制台
let openConsoleBtn = document.getElementById('openConsole');
openConsoleBtn.onclick = function (element) {
  chrome.windows.create({
    url: chrome.extension.getURL("public/index.html"),
    type: "popup",
    height: 480,
  });
};

docend.js

console.log('kSpider:Hello from docend.js.')


/**
 * 用来载入外部函数库
 * 为index.js中的pageRun函数的querystr提供支援
 * @param {*} url   js文件地址,字符串
 * @param {*} onDone    载入后执行的函数onDone() 
 * @param {*} onError   载入失败执行的函数onError(e)
 */
function loadJS(url, onDone, onError) {
    if (!onDone) onDone = function () { };
    if (!onError) onError = function () { };
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            if (xhr.status == 200 || xhr.status == 0) {
                try {
                    eval(xhr.responseText);
                } catch (e) {
                    onError(e);
                    return;
                }
                onDone();
            } else {
                onError(xhr.status);
            }
        }
    }.bind(this);
    try {
        xhr.open("GET", url, true);
        xhr.send();
    } catch (e) {
        onError(e);
    }
}

//载入JQuery
loadJS('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js', function () {
    console.log('juquery ok')
    console.log($("p"))
})

index.html,实际上并不需要这么复杂,只要能实现三个按钮就可以了。

<!DOCTYPE html>
<html>
<body>
  <script src="js/index.js"></script>
</body>
</html>

popup.html

<!DOCTYPE html>

<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="js/lib/twitter-bootstrap/4.4.1/css/bootstrap.css">
  <title>kSpider</title>
</head>

<style>
  .mybtn{
    border-radius: 0px;
    width: 100%;
    color: white;
  }
  .mybtn:hover{
    color: white;
  }
</style>

<body>
  <div class="btn-group-vertical " style="width: 150px;">
    <button id="saveThis" type="button" class="btn-sm btn-warning mybtn">Save This Page</button>
    <hr style="height: 1px;margin: 0;">
    <button id="saveAll" type="button" class="btn-sm btn-warning mybtn">Save All Pages</button>
    <hr style="height: 1px;margin: 0;">
    <button id="openConsole" type="button" class="btn-sm btn-warning mybtn">About kSpider</button>
  </div>

  <script src="js/lib/jquery/3.4.1/jquery.js"></script>
  <script src="js/lib/twitter-bootstrap/4.4.1/js/bootstrap.js"></script>
  <script src="js/popup.js"></script>

</body>

</html>

项目地址

项目已经放在我的网站上开源了,大家可以直接下载到本地,然后浏览器加载这个项目运行使用。

http://10knet.com/zhyuzh/kspider


欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】


每个人的智能新时代

如果您发现文章错误,请不吝留言指正;
如果您觉得有用,请点喜欢;
如果您觉得很有用,欢迎转载~


END

上一篇 下一篇

猜你喜欢

热点阅读