蚊章汇 - CommandlineFu
type: channel
link: https://linuxcn.bwsl.wang/tag/CommandlineFu/
items: 使用 shell 构建多进程的 CommandlineFu 爬虫

使用 shell 构建多进程的 CommandlineFu 爬虫
type: item
date: 2019-03-12T06:45:00+08:00
description: CommandlineFu 是一个记录脚本片段的网站，每个片段都有对应的功能说明和对应的标签。我想要做的就是尝试用 shell 写一个多进程的爬虫把这些代码片段记录在一个 org 文件中。参数定义这个脚本需要能够通过 -n 参数指定并发的爬虫数（默认为 CPU 核的数量），还要能通过 -f 指定保存的 org 文件路径（默认输出到 stdout）。#!/usr/bin/env bash proc_num=$(nproc) store_file=/dev/stdout while getopts :n:f: OPT; do case $OPT in n|+n) proc_num="$OPTARG" ;; f|+f) store_file="$OPTARG" ;; *) echo "usage: ${0##*/} [+-n proc_num] [+-f org_file} [--]" exit 2 esac done shift $(( OPTIND - 1 )) OPTIND=1解析命令浏览页面我们需要一个进程从 CommandlineFu 的浏览列表中抽取各个脚本片段的 URL，这个进程将抽取出来的 URL 存放到一个队列中，再由各个爬虫进程从进程中读取 URL 并从中抽取出对应的代码片段、描述说明和标签信息写入 org 文件中。这里就会遇到三个问题:进程之间通讯的队列如何实现如何从页面中抽取出 URL、代码片段、描述说明、标签等信息多进程对同一文件进行读写时的乱序问题实现进程之间的通讯队列这个问题比较好解决，我们可以通过一个命名管道来实现：queue=$(mktemp --dry-run) mkfifo ${queue} exec 99<>${queue} trap "rm ${queue} 2>/dev/null" EXIT从页面中抽取想要的信息从页面中提取元素内容主要有两种方法：对于简单的 HTML 页面，我们可以通过 sed、grep、awk 等工具通过正则表达式匹配的方式来从 HTML 中抽取信息。通过 html-xml-utils 工具集中的 hxselect 来根据 CSS 选择器提取相关元素。这里我们使用 html-xml-utils 工具来提取：function extract_views_from_browse_page() { if [[ $# -eq 0 ]];then local html=$(cat -) else local html="$*" fi echo ${html} |hxclean |hxselect -c -s "\n" "li.list-group-item > div:nth-child(1) > div:nth-child(1) > a:nth-child(1)::attr(href)"|sed 's@^@https://www.commandlinefu.com/@' } function extract_nextpage_from_browse_page() { if [[ $# -eq 0 ]];then local html=$(cat -) else local html="$*" fi echo ${html} |hxclean |hxselect -s "\n" "li.list-group-item:nth-child(26) > a"|grep '>'|hxselect -c "::attr(href)"|sed 's@^@https://www.commandlinefu.com/@' }这里需要注意的是：hxselect 对 HTML 解析时要求遵循严格的 XML 规范，因此在用 hxselect 解析之前需要先经过 hxclean 矫正。另外，为了防止 HTML 过大，超过参数列表长度，这里允许通过管道的形式将 HTML 内容传入。循环读取下一页的浏览页面，不断抽取代码片段 URL 写入队列这里要解决的是上面提到的第三个问题: 多进程对管道进行读写时如何保障不出现乱序? 为此，我们需要在写入文件时对文件加锁，然后在写完文件后对文件解锁，在 shell 中我们可以使用 flock 来对文件进行枷锁。 关于 flock 的使用方法和注意事项，请参见另一篇博文 Linux shell flock 文件锁的用法及注意事项。由于需要在 flock 子进程中使用函数 extract_views_from_browse_page，因此需要先导出该函数：export -f extract_views_from_browse_page由于网络问题，使用 curl 获取内容可能失败，需要重复获取：function fetch() { local url="$1" while…