最近为了做一些统计,需要到 Tor 这个暗网上采集大量信息。想起很久以前为了研究 Silk Road, EIC, Dr.D, Scream Bitch 和 PlayPen 写过几个爬虫,拿来用 scrapy 重构了一番,做了一个 Tor 暗网通用爬虫。
由于法律风险,此爬虫不开源,仅公开关键部分的代码供参考,此文主要阐述一些暗网爬虫和明网爬虫的区别,爬取注意事项以及开发、生产环境的设置。
所有代码都按 MIT License。
基础
首先,在 Tor 网络上爬东西和在明网上并没有多少不同,因为 Tor 方面只是在本地开了一个 socks5 代理,基本上让爬虫走 tor 的方法和设置程序走任何 socks5 代理的方法是一样的。在中国由于 shadowsocks 的盛行所以实际上没有太多挑战,设置方法都一样,指定 all_proxy 为 socks5://127.0.0.1:9150 即可。
因为 dns 查询默认是不走 tor 的 socks5 代理的,如果直接把爬虫的 all_proxy 指过去却不管 dns,所有 .onion 域名当然是 NXDOMAIN 了。。所以爬虫内设置代理一定要注意这些。
scrapy 有自己支持 HTTP 代理,但是我们可以不用这个代理,要走暗网的话当然是期望一丝不漏地把流量全都从暗网丢出去。。所以可以把请求都 hook ,走我们指定的代理。这里就可以用 proxychains 这个软件 (mac 上是 proxychains-ng)。考虑到需要 http 代理,然而 tor 提供的是 socks5,可以先用 polipo 或者 privoxy 转一下,方法也简单,还是本地开一个,设置好转发就可以了。
DNS的设置方法
scrapy 的 dns lookup 实现是在 scrapy/scrapy/resolver.py 文件中调用 twisted 模块的 twisted.internet.base.ThreadedResolver(object)
[相关文档](https://twistedmatrix.com/documents/16.6.0/api/twisted.internet.base.ThreadedResolver.html)
而 twisted 在解析域名的过程中实质上调用的是 python 标准库 socket 模块的方法 socket.gethostbyname(hostname)
[文档](https://docs.python.org/3/library/socket.html#socket.gethostbyname)
你可以从这些模块中间的任意位置实现一个装饰器把 dns lookup hook 到 tor 代理上。
如果不需要长期部署的话,也可以直接用 proxychains 等程序直接强制 python 走本地代理。比方说在本地设置好 tor 代理 (假设开的本地端口是9150) 后,在本地新建一个proxychains的配置文件如下
# proxychains.conf VER 4.x strict_chain proxy_dns remote_dns_subnet 224 tcp_read_time_out 15000 tcp_connect_time_out 8000 [ProxyList] socks5 127.0.0.1:9150
然后以新的配置文件通过 proxychains 启动 scrapy 即可
爬虫策略
暗网和明网的一个超大的区别就在于暗网上没有 javascript 代码,所以爬取结果就只有一个静态文件直接处理,非常方便。
因为 tor 本身就是匿名代理,也不用再代理换 ip 了,省钱了。
唯一比较有问题的就是账号访问权限问题,因为暗网很多网站服务端是会记录用户访问的,最好多开几个账号(反正不用验证邮箱,验证码也基本上都是儿戏,机器批量注册几百个就行了),账号分给不同的 slave 去爬,但是最好注意 referer 的逻辑上的连贯性。。(不要从不可能的位置“点击”进某个页面去。。)
每个 slave 一定要新建一个 tor ciucuit,否则会大幅拖慢爬取的速度。
程序设计方面
-
爬取策略
暗网的论坛程序一般都比较简陋,很多程序 thread 竟然是用数字 id 的方法排序的,所以要全部爬的话最佳方法就是遍历 thread id。
-
登录设计
登录方面都很容易设计,因为没有 javascript,服务端性能也一般,网站登录设计大多相同,直接构造数据 post 即可,有验证码的一般也多为固定题库或者简单的 php 生成的小图片,很容易程序识别。
比如说,这里是爬取 Magic Kingdom 的工程 demo 文件,可以看出只需要调用 scrapy 的 FormRequest.from_response 就可以方便地登录。。
# File: mks/mk/spiders/m.py import scrapy import logging import re from mk.items import MkItem posts_number = re.compile(r'''(\d+) posts''') class MkSpider(scrapy.Spider): name = 'MagicKingdom' start_urls = ['http://nj************og.onion/index.php'] allowed_domains = ['nj************og.onion'] def parse(self, response): yield scrapy.FormRequest.from_response( response, method='POST', # headers={'Content-Type': 'multipart/form-data'}, url='http://nj************og.onion/ucp.php?mode=login', formdata={ 'username': 'BadUsErnAME', 'password': 'N0TaGoOdPa$sw0Rd123', 'login': 'Login', 'redirect': '.%%2Findex.php%%3F', }, callback=self.parse_index ) def parse_index(self, response): for i in range(16700): yield scrapy.Request( 'http://nj************og.onion/viewtopic.php?t={}'.format(i), callback=self.parse_thread, meta={'i':i} ) def parse_thread(self, response): t = response.meta['i'] t_title = response.xpath('//title/text()')[0].extract() if 'Information' in t_title: item = MkItem() item['url'] = response.url item['title'] = '404' item['thread_content'] = [] yield item else: l = response.xpath('//div[@class="pagination"]/text()').extract() d = 0 for i in l: if 'posts' in i: d = int(posts_number.search(i).group(1)) break if d == 0: logging.log(logging.INFO, 'No pagination found for {}'.format(response.url)) d = 1 for i in range(0, d, 10): yield scrapy.Request( 'http://nj************og.onion/viewtopic.php?t={}&start={}'.format(t,i), callback=self.parse_thread_page ) def parse_thread_page(self, response): item = MkItem() item['url'] = response.url item['title'] = response.xpath('//title/text()')[0].extract() item['thread_content'] = response.xpath('//div[@class="content"]').extract() yield item
-
多媒体处理
tor 暗网的另一个特色是它的图床和 Referer 检查,我们的爬虫爬到的 img_list 常常混杂很多 onion 上的图床,这些图床一方面只能通过 tor 访问,另一方面尝尝会检查 referer 从而确定访问来源,为了避免日后需要下载这些图片时麻烦,建议爬取的时候都把 url 记录下来备用。另外,silkroad (尤其是 reloaded 站) 上大量垃圾卖家会上传很多 duplicate 的图片,更多时候甚至是直接盗链,onion 图床一般又巨慢,先检查 url 去重再下载能节省不少时间。
-
二进制文件和文件链接
由于 tor 暗网在很多地区速度较慢,很难直接传输大文件,因此很多文件传递是通过 anonfiles 等 clear web service 实现的。由于很多论坛缺乏有效管理,很多情况下这些文件的下载地址在页面中的出现位置不定,file host 也常常变化。同时因为传递的文件通常非法,一般都经过严密的加密,并且常常会上传多份镜像,而解压密码也常常混杂在页面内容中难以分离。
可以说,从这些位置爬取到的数据非常脏,数据清洗不是一件容易的事,如果要下载这些文件,需要付出相当的劳动。
不过因为之前爬取了整个 sis 论坛和草榴论坛积累了大量类似数据,我在今年早些时候已经用机器学习做了一个简单的提取模型,通过 NLP 分词后做 classification,对中英文都有奇效。。
- 对每个文件,都在其来源处尝试提取 possible specific passwords。
- 通常帖子作者都会在原贴内标出密码,对帖子的内容分词后做 pattern match,is_password 的 possibility 大于临界值就归类为 possible specific passwords。
- 用 possible specific passwords 依次尝试解密文件,如果成功,该密码加入 all passwords dictionary,同时提升该 pattern 的权重。
- 大量数据输入(因为在学习的早期 model 不成熟,利用很低,数据有限不能浪费。。。所以我尝试了复用一次学习早期输入的数据,效果不错)
- 循环学习爆破,当几乎不再有 specific password 出现的时候,把仍然未能解密的文件标记出来,用之前成功过的密码(all passwords dictionary)爆破(原理是有很多发帖人在不同的帖子里会复用同一个密码,比如四散的尘埃、扶她奶茶之类)我用 sis 和 草榴 的数据训练了一个模型,然后去处理了 magic kingdom 的大约 10000 个thread,效果良好(何止是良好简直有奇效)。4152个文件解密后仅剩余44个文件不能解密,剩余的文件里手动查看之后发现多数都是来自同一家 file host,可能是该 file host 被恶意劫持,从它家下载的文件都不能正确解密,重新下载也没用,遂放弃。另一些则是密码提取有问题,不过这么多文件只需要手动解密几个,我已经很满足了。。。而处理 giftbox exchange 这样的论坛的时候因为论坛发帖格式非常严格,所以几乎所有文件都成功解密。这个策略由于早期和末期涉及遍历爆破,所以有效程度和 data size 关系密切。。如果数据太多会花费很长时间,不过因为 tor 暗网网站内容普遍不像其它暗网那么多,所以这个勉强算 effective solution 吧。。
-
下载策略
暗网上共享的文件常常上传到不同的 file host,图片也常常上传到多个 image host。方便地从这些地方下载文件的方法是将所有下载链接提取之后通过 xml-rpc 调度 aria2 统一下载。具体方法相当简单,可以写外部脚本,也可以内置到爬虫中。例如内置到 python 中:
import xmlrpc.client # 先在远程服务器上以合适的配置文件启动 aria2, 并让它监听rpc port 6800,然后再执行以下代码 server_uri = 'http://username:[email protected]:6800/rpc' # 这里为了 demo 方便用了 http basic auth,在生产环境中为了安全请勿使用。请使用 token 授权。 proxy = xmlrpc.client.ServerProxy(server_uri, allow_none=True) proxy.aria2.addUri(list_of_uris, dict(dir=directory_to_save))
請問,能否跟你要原始碼呢
Sorry, 暂时不打算分发源码。但是主要的架构和坑都已经写出来了,去github上找些开源组件拼接一下就可以了
你好我想跟你联系一下。方便可以回个邮件么
虽然只是找普通爬虫资料找到这,不过收获还是很大的,谢谢
这个源代码是否可以分享呢?
这个源代码是否可以分享呢?
最近在做这块、有很大的迷茫点需要助希望可以跟您联系一下。回复一下我谢谢啦
直接给我发邮件就可以,我的邮件地址在about页面
博主有源码吗??
您好 ,能讲下 privoxy 和 shadowsocks 在整个过程中的作用吗…
我的理解是 privoxy 将 http流量转换成 sock5的流量 ,然后给tor,tor之后交给shadowsocks只是简单加密做个转发,然后到tor真正的网络中,是这样的吗…跪求解答下…
已经回复你的邮件了。
实际上并不需要 Shadowsocks, 我只是用 shadowsocks 举个例子。
这样就可以
crawler -> (http) -> privoxy(127:0.0.1:8118) -> (socks5) -> Tor(127.0.0.1:9050) -> (socks5) -> Tor-network
当然在中国访问 tor 很慢,可以用shadowsocks中转来加速,那样的话就是像你说的一样。