dotnet 爬虫的神

这一周干了两次需要批量下载图片或文件的自动化工作,连续两次使用都是 dotnet 来完成,着实不错,尤其是配合 c# 的 async/await 语法糖,异步下载性能确实够快。这两次的下载让我对于这种需要批量获取信息或者下载的任务解锁了新的姿势。

首先需要明确一点,我们需要的只是爬虫的结果而已,只是为了能够自动化获取我们所需要的数据或资源,所以我们所做的事情,所写的代码即使稍微 dirty 点也无妨,只要能 work 就好。尤其是在工程中,过于追求所谓的 elegant 只会让自己痛苦不已,不过这些都是题外话了。

So glad to make it dirty. 爬虫遇到例外情况时就做特殊判断,有异常就针对异常对个例单独处理,全自动化不行就手工做点操作加点信息。

html parsing

html 的解析库用的是 Html Agility Pack,可以解析字符串,也可以直接发请求获取页面来解析。其使用 XPath 作为选择器,不支持 css 选择器,学过 XPath 的可以接受,而且 XPath 的表达性足够的强,足够使用。

code sample 如下,能够根据 XPath 选出特定的节点,然后取出其中的属性和文本其实已经差不多了。

1
2
3
4
var html =  await new HtmlWeb().LoadFromWebAsync(url);
var node = html.DocumentNode.SelectSingleNode(xpath);
var text = node.InnerText;
var attr = node.GetAttributeValue("href", defaultValue);

download

完全就是根据 dotnet 的 API 来操作的,重点推荐 System.Net.Webclient 的 DownloadFileTaskAsync 方法,直接根据 url 开启异步下载任务,开启下载任务后将所有的 tasks 一起等待运行结束即可。
这里有个需要注意的点是,API 内置的 WebClient 是不支持并行的,这意味着一个 Client 发请求后必须完成任务后才能用它进行下个任务,所以需要并行的时候建议多开几个 client。

sample 如下

1
2
3
4
5
6
7
var tasks = new List<Task>();
for(var url in urls)
{
tasks.Add(new WebClient().DownloadFileTaskAsync(new Uri(url), path));
}

await tasks.WhenAll(tasks);

tricks

  • 需要下载或者访问的 urls 有时并不需要通过代码中发请求获取页面解析这样的方式来获取,很多时候直接在开发者工具里面用 XPath 来取出相应的 urls 更加简单快捷
    1
    $x("//a[@id='target node']").map(x=>x.href)
  • 并不需要一次性就能把全部数据抓取下来,不妨合理分批分成多个任务来完成,这样 fail 一次也只需重新执行改部分,而不需要全部重来。

performance

性能只能说好得离谱,也可能是因为之前的 Python 爬虫给我留下太糟糕的印象作为 baseline 了。这主要得益于 dotnet 本就良好的性能,以及其支持得很好的异步编程设计,基本每个可能会阻塞的 API 都提供了 async 版本。

  • 下载了 90 posts,包括其中的文字和图片,每个 post 平均有两张图片,加起来用了一分钟左右。
  • 下载了差不多 60 个 pdf,用来不到 20s