通过 Bash 脚本阅读网络漫画

作者:Jim Hall

我关注几个网络漫画。我过去常常打开网页浏览器并查看每个漫画的网站。当我只看几个网络漫画时,这种方法还不错,但是当我关注超过十个漫画时,保持更新就成了一种痛苦。现在,我阅读大约 20 个网络漫画。仅仅为了阅读一个网络漫画,单独打开每个网站需要花费大量时间。我可以将网络漫画加入书签,但我想一定有更好的方法——一种更简单的方法让我一次阅读所有网络漫画。

因此,我编写了一个 Bash 脚本,我将其指向我喜欢的任何网络漫画。该脚本非常简单:它下载网络漫画的网页,然后查看页面上的每个图像,并尝试找出哪个是漫画。

首先让我告诉您我在这个脚本中使用的三个重要工具

  1. 您可能已经熟悉第一个工具:wget 将使用 http 或 https 从 Web 下载文件。
  2. 第二个工具是 ImageMagick。它实际上是一组用于处理图像的命令行工具。您可以使用 ImageMagick 做很多事情,但在本脚本中,我专注于两个关键功能:identify 用于获取有关图像的属性,以及 convert 用于将图像从一种格式转换为另一种格式。
  3. 另一个工具对我来说是新的:xmllint。作为 Libxml 项目 的一部分,您可以使用 xmllint 查询 XML 或 HTML 文档并从中检索数据。我可以写很多关于 xmllint 的内容。像任何有用的工具一样,您可以利用 xmllint 做很多不同的事情。程序员可以使用 xmllint 来解析 XML 文档以获取特定值,例如 Web 服务系统中的成功值。但在本 Bash 脚本中,我使用 xmllint 从 HTML 文档中打印特定标签。您的系统应该已经安装了 xmllint;它几乎是每个 Linux 发行版中的标准软件包。

我假设您已经熟悉标准的 UNIX 命令行工具,包括 head 和 awk。

有了这些工具,您可以构建一个 Bash 脚本来从网页获取网络漫画并保存以供稍后查看。该过程包括五个简单的步骤

  1. 下载网络漫画的网页并将相对链接转换为绝对链接 (wget)。
  2. 查找网页中引用的每个图像 (xmllint)。
  3. 下载这些图像 (wget)。
  4. 查找最大的图像 (identify)。
  5. 转换该图像以供稍后查看 (convert)。

核心假设是网络漫画将是网页上最大的图像(按尺寸)。这通常是正确的。当您考虑到网站的徽标、社交媒体图标和其他装饰时,网络漫画通常是页面上最大的图像。但有时艺术家会在他们的网站上分享其他媒体,例如会议照片或粉丝艺术作品。这些次要图像可能比网络漫画更大。在这种情况下,Bash 脚本将获取其中一个次要图像而不是网络漫画,因此您的结果可能会因网站而异。但在大多数情况下,这个假设效果很好。

自动化此过程的一个问题是您永远不知道网站将如何编码。图像标签可能使用双引号或单引号。或者图像可能以不同的顺序使用 src=alt=。一些网站可能会在图像文件名中使用空格或其他奇怪的字符。幸运的是,使用 xmllint 可以更容易地解决所有这些问题。无论输入如何,xmllint 都将使用双引号并使用 HTML 转义空格返回数据。这是 xmllint 的默认功能;您无需调用任何命令行选项来清理 xmllint 的输出。

让我们逐步了解这个过程

第一步,下载网页并将链接转换为绝对链接,使用 wget 可以轻松完成。有一个方便的命令行选项可以自动转换链接


wget --convert-links -O /tmp/index.html http://www.example.com/comic/

这会获取网页并将其保存为 /tmp/index.html。下载完成后,wget 会转换链接,因此引用为 /images/comic.png 的图像将转换为类似于 http://www.example.com/images/comic.png 的内容。

一旦您有了网页的本地副本,您就可以使用 xmllint 的强大功能仅从文档中提取图像。使用 xmllint 的方法有很多种。关键是指定一个 Xpath 表达式。Xpath 是与 XML 或 HTML 文档交互的标准语法,使用表达式来仅识别您想要的文档部分。在这种情况下,您对文档中引用的图像感兴趣。请记住,HTML 文档中图像标签的标准语法如下所示


<img src="/images/comic.png" alt="Today's comic" />

图像标签可以出现在 HTML 文档中的任何位置,而用于提取任何 <img> 标签(无论其位置如何)的 Xpath 是 //img 表达式


xmllint --html --xpath '//img' /tmp/index.html

但具体来说,您想要图像标签的 src= 部分。您可以通过扩展 Xpath 表达式非常容易地做到这一点。在 Xpath 中,@ 选择一个属性。因此,要获取文档中所有 <img> 标签的 src= 属性,您可以修改 xmllint 命令中的 Xpath 表达式


xmllint --html --xpath '//img/@src' /tmp/index.html

理想情况下,我只想获取 src= 属性的值,但我不知道如何通过单个 Xpath 表达式来做到这一点。此 xmllint 命令的输出是一行,列出了 HTML 页面中引用的每个图像的每个 src= 属性。因此,您将得到类似这样的内容


src="http://www.example.com/logo.png"
 ↪src="http://www.example.com/i/comic.png"

方便的是,图像链接将始终是绝对链接,因为这就是 wget 转换网页的方式。输出的关键是围绕每个图像 src= 引用使用双引号。请记住,xmllint 始终使用双引号返回数据,即使输入使用单引号也是如此。有了这个假设,修复就很容易了。快速通过 awk 返回图像列表:使用双引号作为字段分隔符,并查看每个其他字段。


xmllint --html --xpath '//img/@src' /tmp/index.html |
 ↪awk -F\" '{for (i=2; i<NF; i+=2) {print $i}}'

从那里开始,就是要依次获取每个图像并弄清楚每个图像有多大。ImageMagick 中的 identify 命令在这里有所帮助。您可以指示 ImageMagick 以 Bash 可以评估为算术表达式的格式打印图像尺寸


identify -format '%W * %H\n' /tmp/comic.png

identify 的输出打印宽度和高度,您可以要求 Bash 计算(例如,1024 * 539 的计算结果为 551,936)。对于图像列表,您可以计算每个图像的大小,并使用 sort 找到最大的图像。核心假设是网络漫画将是网页上最大的图像(按尺寸),因此这是您复制以供稍后使用的图像。

有了这些部分,您可以组装最终的 Bash 脚本来获取网络漫画。此脚本的命令行提供 URL 和网络漫画文件的本地副本名称,该副本以 JPEG 格式保存


#!/bin/bash

# Usage: fetch-comic.sh URL Name

if [ $# -ne 2 ] ; then
        echo 'Usage: fetch-comic.sh URL Name'
        exit 1
fi

URL=$1
NAME=$2

TMPDIR=$HOME/tmp
TMPFILE=$TMPDIR/index.tmp
CACHEDIR=/var/www/html/comics
USERAGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36
 ↪(KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36'

# fetch web page

wget --timeout=60 --user-agent="$USERAGENT" --convert-links
 ↪--quiet -O $TMPFILE $URL

# list images

# Since xmllint is (surprise!) a linter, it will squawk
# about the inevitable garbage in your html stream.
# We direct the stderr to /dev/null in the usual way:

images=$( xmllint --html --xpath '//img/@src' $TMPFILE 2>/dev/null
 ↪| awk -F\" '{for (i=2; i<NF; i+=2) {print $i}}' )

# find biggest image

imgcount=0

maximg=$(
  ( for imgURL in $images ; do
        wget --timeout=60 --user-agent="$USERAGENT" --quiet
 ↪--referer=$URL -O $TMPDIR/$imgcount $imgURL
        echo $(( $( identify -format '%W * %H\n' $TMPDIR/$imgcount
 ↪| head --lines=1 ) )) $TMPDIR/$imgcount
        imgcount=$(( imgcount + 1 ))
    done
  ) | sort -n -r | awk 'NR==1 {print $2}'
)

# convert the image

if [ $( file --mime-type --brief $maximg ) = 'image/gif' ] ; then
        # convert GIF to JPG
        convert "$maximg[0]" -resize 740\> $CACHEDIR/$NAME.jpg
else
        # convert to JPG
        convert $maximg -resize 740\> $CACHEDIR/$NAME.jpg
fi

我不得不在 Bash 脚本中添加一些额外的功能,主要是为了适应动画 GIF 图像。如果您向 ImageMagick 的 identify 实用程序询问动画 GIF 的尺寸,它会为动画中的每一帧返回一个单独的条目。因此,在循环中,我将图像大小计算限制为第一帧。同样,ImageMagick 的 convert 工具将动画 GIF 转换为 JPEG 格式会为每个动画帧生成单独的图像。我在最后添加了一个 if 语句,仅引用 GIF 图像的第一帧。

为了使本地查看更容易,我使用 convert 的一个特殊选项,将新图像的最大宽度限制为 740 像素。如果输入宽度大于此值,则整个图像将按比例缩小。较小的图像不会调整大小。

我每天早上通过 cron 在我家里的个人服务器上运行此 Bash 脚本。图像被复制到我的 Web 服务器上的 /comics 目录中。我编写了一个简单的静态网页来显示网络漫画图像。

因此,当我开始新的一天时,我只需打开我的 Web 浏览器到本地网页,然后一次阅读所有网络漫画。这非常节省时间。我不再需要访问 20 个不同的网站来获取每日网络漫画,而是使用一个简单的 Bash 脚本来为我自动化这项工作。像任何优秀的程序员一样,我预先付出了一些额外的努力,让剩下的日子轻松很多——即使只是为了网络漫画。

Jim Hall 是一位开源软件倡导者和开发者,可能最出名的是 FreeDOS 的创始人。Jim 在 GNOME 等开源软件项目的可用性测试中也非常活跃。在工作中,Jim 是 Hallmentum 的 CEO,这是一家 IT 执行咨询公司,帮助 CIO 和 IT 领导者进行战略规划和组织发展。

加载 Disqus 评论