浏览器功能定制5:搜索引擎命令

在前面的探索中,主要做了两件事:一是将网页转为 Markdown,而是给浏览器添加了网络拦截能力。再加上 qutebrowser 本质上就是一个大 SDK。我在向解决信息过载问题继续迈进。

今天探索一个新的话题,创建一条新命令,打通搜索引擎。本文以 bing 搜索引擎为例。

基于 qutebrowser 实现流程如下:

  1. 跳转到搜索引擎
  2. 解析渲染后的 HTML,提取出信息条目
  3. 浏览下一页,重复 Step4
  4. 直至命中结束条件

扩展加载完毕的监听

在自定义命令中,拿到 AbstractTab 代表当前 Tab,调用 load_url 可以跳转地址。

但是对于页面加载完成的监听,AbstractTab 没有提供回调方法。因此修改 qutebrowser 代码,在 AbstractTab 中添加回调注册能力。

首先在 AbstractTab 添加一个数组属性,用于保存回调。

on_load_finish_cbs: List[Callable[[bool], None]] = []

在 AbstractTab 已有的槽函数中,遍历调用:

@pyqtSlot(bool)
def _on_load_finished(self, ok: bool) -> None:
    assert self._widget is not None
    if self.is_deleted():
        return

	# 新增
    for cb in self.on_load_finish_cbs:
        cb(ok)

	# ……

置于回调的注册与删除,图省事,调用方直接访问 on_load_finish_cbs 即可。

这样,命令可以形成如下的框架:

def feed_bing(cur_tab: browsertab.AbstractTab,
              keyword: str):
    url = f"https://cn.bing.com/search?q={keyword}"

    def on_load_finish(ok: bool):
        cur_tab.on_load_finish_cbs.remove(on_load_finish)
        if not ok:
            return

    cur_tab.on_load_finish_cbs.append(on_load_finish)
    cur_tab.load_url(QUrl(url))

新命令 bing

接下来创建一条新命令,就叫 bing,其特点是通过交互式命令来输入关键词。

@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('keyword')
def bing(self, keyword=None):
    feed_bing(self._cntwidget(), keyword)

这样,在 qutebrowser 中输入 :bing ubuntu 即可跳转到搜索引擎。

提取页面元素

我能拿到 HTML。如何将页面内容高效提取出来呢?答案是 XPath。我的 XPath 技能仍然比较生疏,花了不少时间才提取成功,如果熟练的话,这个过程会很快。

封装函数:

def extract_info(html: str):
    tree = etree.HTML(html)
    results = tree.xpath('//ol[@id="b_results"]/li')
    for result in results:
        title = result.xpath('string(.//h2)')
        url = result.xpath('.//h2/a[1]/@href')
        if len(url) == 0:
            continue
        description = result.xpath('string(.//p)')
        print("===================")
        print(title)
        print(url)
        print(description)
        print("===================")

效果:

===================
Install | Flutter
['https://docs.flutter.dev/get-started/install']
Web1 day ago · Install Flutter and get started. Downloads available for Windows, macOS, Linux, and ChromeOS operating systems.
===================
===================
安装和环境配置 - Flutter 中文文档 - Flutter 中文开发者网站 ...
['https://flutter.cn/docs/get-started/install']
WebAug 5, 2023 · Flutter安装和上手起步教程, 下载 Windows、macOS、Linux 和 ChromeOS 系统的 Flutter SDK。
===================

翻页

qutebrowser 中有一个神奇命令——navigate,它专门处理这种多页页面的上一页、下一页问题。

首先,通过 functools.partial 封装出一个简版的下一页函数:

next_page_func = functools.partial(self.navigate, 'next')

整体逻辑代码如下

cur_page = 0


def feed_bing(cur_tab: browsertab.AbstractTab,
              keyword: str,
              next_page_func: callable = None):
    url = f"https://cn.bing.com/search?q={keyword}"

    def on_load_finish(ok: bool):
        global cur_page
        cur_tab.on_load_finish_cbs.remove(on_load_finish)
        if cur_page == 3:
            cur_page = 0
            return

        if not ok:
            return

        def on_html(html: str):
            global cur_page
            print('extracting page: ', cur_page)
            extract_info(html)

            if cur_page < 3:
                cur_page += 1
                cur_tab.on_load_finish_cbs.append(on_load_finish)
                next_page_func()

        cur_tab.dump_async(on_html)

    cur_tab.on_load_finish_cbs.append(on_load_finish)
    cur_tab.load_url(QUrl(url))

注:代码有点乱,而且有全局变量不好维护,后续考虑封装成一个类。

翻页命令 navigate 实现原理

首先,browsertab 对 DOM 也有一层封装,并且支持 find_css 以 CSS 进行查询。代码如下:

browsertab.elements.find_css(
	link_selector, 
	callback=_prevnext_cb,
    error_cb=lambda err: message.error(str(err)))

_prevnext_cb 是一个回调。内部调用 _find_prevnext 寻找符合要求的元素。从注释中我们能理解它的规则:

"""Find a prev/next element in the given list of elements."""
# First check for <link rel="prev(ious)|next"> as well as
# e.g. <a class="nav-(prev|next)"> (Hugo)

简单理解为预制了一些规则,bing 就正好满足这一规则。

小结

在本文中我实现了:bing 命令,而且能够自动翻页。这个命令手动调用很方便,更多的是为后续进行自动化信息采集做铺垫,后者的价值更高。


本文作者:Maeiee

本文链接:浏览器功能定制5:搜索引擎命令

版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!


喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!