用于获取DNS域及其相关服务的分布式爬虫。
项目描述
dns-crawler
用于获取(可能数量庞大的)DNS域信息的爬虫
它做什么?
尽管名称如此,该爬虫获取的信息远不止DNS服务
- DNS
- 所有A/AAAA记录(对于二级域名和“www.”子域名),可选地带有GeoIP注释
- TXT记录(带有解析SPF和DMARC以简化过滤)
- TLSA(对于二级域名和“www.”子域名)
- MX
- DNSSEC验证
- 名称服务器
- 每个服务器IP可选地带有GeoIP注释
- HOSTNAME.BIND, VERSION.BIND, AUTHORS.BIND和fortune(以及所有IP)
- 用户可以在配置文件中添加自定义的附加RR
- 电子邮件(来自MX记录的每个服务器)
- SMTP服务器横幅(可选,端口可配置)
- TLSA记录
- 网页
- 每个IP上80和443端口的HTTP状态和头信息(包括解析的cookies)
- HTTPS证书信息(可选,包括整个证书链)
- 网页内容(可选)
- 上述所有内容都保存为重定向历史中的每个步骤 – 爬虫会跟随重定向,直到它获得非重定向状态或达到可配置的限制
域名服务器和邮件服务器的答案被缓存,因此爬虫不会对托管提供商进行重复查询。
如果您需要配置防火墙,爬虫将连接到端口53
(UDP和TCP),25
(TCP),80
(TCP),和443
(目前为TCP,但我们可能会添加UDP与HTTP3…)。
查看result-example.json
以了解生成的JSON的外观。
它到底有多快?
一台相当现代的笔记本电脑在约50Mbps的连接下,可以整夜爬取整个.cz区域(约130万个二级域名),具体时间可能会有所不同,使用每个CPU线程8个工作者。
由于爬虫被设计成并行,实际速度几乎完全取决于工作者数量。它几乎可以无限地跨多台机器扩展,所以如果您需要在1小时内爬取一百万个域名,您总是可以简单地投入更多硬件(见下文)。
CZ.NIC在生产中使用4台机器(8核Xeon Bronze 3106,16 GB RAM,千兆线)来爬取整个.cz区域,耗时不到3小时。
安装
创建并激活一个虚拟环境
mkdir dns-crawler
cd dns-crawler
python3 -m venv .venv
source .venv/bin/activate
安装dns-crawler
pip install dns-crawler
根据您的操作系统/发行版,您可能需要安装一些系统软件包。在Debian/Ubuntu上,apt install libicu-dev pkg-config build-essential python3-dev
应该可以解决问题(当然,假设您已经安装了python3)。
基本使用
要运行单线程爬虫(适用于少量域名),只需传递一个域名列表
$ echo -e "nic.cz\nnetmetr.cz\nroot.cz" > domain-list.txt
$ dns-crawler domain-list.txt > results.json
[2019-12-03 11:03:54] Reading domains from domain-list.txt.
[2019-12-03 11:03:54] Read 3 domains.
[2019-12-03 11:03:55] 1/3
[2019-12-03 11:03:55] 2/3
[2019-12-03 11:03:56] 3/3
[2019-12-03 11:03:56] Finished.
结果打印到stdout - 每个域的JSON,由\n
分隔
$ cat results.json
{"domain": "nic.cz", "timestamp": "2019-12-03 10:03:55", "results": {…}}
{"domain": "netmetr.cz", "timestamp": "2019-12-03 10:03:55", "results": {…}}
{"domain": "root.cz", "timestamp": "2019-12-03 10:03:56", "results": {…}}
如果您想获得格式化的JSON,只需将输出通过jq或您选择的工具:dns-crawler domain-list.txt | jq
。
多线程爬取
首先,您需要一个正在运行并监听的Redis服务器。
爬虫可以运行多个线程以加快处理大量域名的速度。控制器和工作者之间的通信通过Redis完成(这使得如果需要,在多台机器上运行工作者变得容易,见下文)。
启动Redis。具体命令取决于您的系统。如果您想使用不同的机器来运行Redis和爬虫控制器,请参阅dns-crawler控制器的CLI参数。
将域名喂入队列并等待结果
$ dns-crawler-controller domain-list.txt > result.json
(在另一个shell中)启动工作者,处理域名并将结果返回给控制器
$ dns-crawler-workers
使用控制器还可以免费获得重复查询的缓存(邮件服务器横幅和nameservers的hostname.bind/version.bind)。
Redis配置
不需要特殊配置,但如果有大量域名要处理,请增加内存限制(例如maxmemory 2G
)。您还可以禁用磁盘快照以节省一些I/O时间(取消注释save …
行)。如果您还没有使用Redis做其他事情,请阅读它的日志 – 常常有关于性能改进的建议。
结果
结果打印到主进程的stdout(dns-crawler
或dns-crawler-controller
) – 每个域的JSON,由\n
分隔
…
[2019-05-03 07:38:17] 2/3
{"domain": "nic.cz", "timestamp": "2019-09-24T05:28:06.536991", "results": {…}}
…
进度信息带时间戳打印到stderr,因此您可以轻松保存输出 – dns-crawler list.txt > results
。
输出JSON的JSON模式已包含在仓库中: result-schema.json
,以及nic.cz的示例: result-example.json
。
有多个用于模式验证、查看甚至代码生成的工具。
要验证结果与模式(CI已配置自动执行)
$ pip install check-jsonschema
$ check-jsonschema --schemafile result-schema.json result-example.json
或者,如果你不讨厌JS,ajv
有更好的输出
$ npm i -g ajv-cli
$ ajv validate -s result-schema.json -d result-example.json
存储爬虫结果
在生产中,CZ.NIC使用Hadoop集群在爬虫运行完成后存储结果文件——见utils/crawler-hadoop.sh
脚本(将结果文件推送到Hadoop并通知Mattermost频道)。
你甚至可以直接将输出重定向到Hadoop,而不需要在你的磁盘上存储它
dns-crawler-controller domain-list.txt | ssh user@hadoop-node "HADOOP_USER_NAME=… hadoop fs -put - /path/to/results.json;"
处理结果
在Python代码中的使用
只需像这样导入并使用process_domain
函数:
$ python
>>> from dns_crawler.crawl import process_domain
>>> result = process_domain("nic.cz")
>>> result
{'domain': 'nic.cz', 'timestamp': '2019-09-13T09:21:10.136303', 'results': { …
>>>
>>> result["results"]["DNS_LOCAL"]["DNS_AUTH"]
[{'value': 'a.ns.nic.cz.'}, {'value': 'b.ns.nic.cz.'}, {'value': 'd.ns.nic.cz.'}]
process_domain
函数返回Python dict
。如果你需要JSON,请使用from dns_crawler.crawl import get_json_result
。
$ python
>>> from dns_crawler.crawl import get_json_result
>>> result = get_json_result("nic.cz")
>>> result
# same as above, just converted to JSON
此函数只是调用crawl_domain
并将dict
转换为JSON字符串。它由工作者使用,因此转换由他们完成,以减轻控制器进程的压力。
配置文件
GeoIP数据库路径、DNS解析器IP、超时以及其他一些内容来自工作目录中的config.yml
(如果存在)。
默认值列在config.yml
中,其中包含解释性注释。(config.yml
)
如果你使用多线程爬虫(dns-crawler-controller
和dns-crawler-workers
),配置由控制器加载并与工作者通过Redis共享。
如果需要,你可以在工作者机器上覆盖它——只需在工作目录中创建一个config.yml
(例如,为设置不同的解析器IP或GeoIP路径在每个机器上)。然后配置将被合并——未在工作者配置中定义的指令将从控制器中加载(如果在那里也没有定义,则使用默认值)。但是——根据你更改的值——你可能会从每个工作者机器得到不同的结果。
GeoIP注释
为此功能正常工作,你需要获取爬虫使用的GeoIP数据库。它支持付费和免费数据库(注册后可在此下载)。
爬虫期望它们在/usr/share/GeoIP
(Maxmind的geoipupdate默认将其放置在那里),但这可以在配置文件中轻松更改
geoip:
enabled: True
country: /path/to/GeoLite2-Country.mmdb
asn: /path/to/GeoLite2-ASN.mmdb
使用商业(GeoIP2 Country和ISP)数据库而不是免费(GeoLite2 Country和ASN)数据库
geoip:
enabled: True
country: /usr/share/GeoIP/GeoLite2-Country.mmdb
# asn: /usr/share/GeoIP/GeoLite2-ASN.mmdb # 'asn' is the free DB
isp: /usr/share/GeoIP/GeoIP2-ISP.mmdb # 'isp' is the commercial one
(使用绝对路径或相对于工作目录的路径)
如果同时定义了,则首选付费的ISP
(付费)数据库而不是免费的ASN
(免费)数据库。差异在Maxmind网站上描述:https://dev.maxmind.com/faq/what-is-the-difference-between-the-geoip-isp-and-organization-databases/。
免费的GeoLite2-Country
似乎有点不准确,特别是在IPv6的情况下(它将一些CZ.NIC名称服务器放置在乌克兰等地方)。
获取额外的DNS资源记录
你可以轻松获取一些额外的RR(对于二级域名),这些RR默认情况下不包括在爬虫中
dns:
additional:
- SPF
- CAA
- CERT
- LOC
- SSHFP
见DNS记录类型列表以获得一些想法。但是,像OPENPGPKEY这样的东西将不起作用,因为它们旨在在子域中使用(在这种情况下,它们是电子邮件地址部分的散列)。
您可以通过向dns_utils.py
文件中的additional_parsers
枚举中添加一个函数来为记录插入解析器。默认情况下只包含SPF(因为已弃用的SPF记录格式与TXT中的SPF格式相同,而爬虫默认获取的就是TXT中的SPF)。
命令行参数
dns-crawler
dns-crawler - a single-threaded crawler to process a small number of domains without a need for Redis
Usage: dns-crawler <file>
file - plaintext domain list, one domain per line, empty lines are ignored
dns-crawler-controller
dns-crawler-controller - the main process controlling the job queue and printing results.
Usage: dns-crawler-controller <file> [redis]
file - plaintext domain list, one domain per line, empty lines are ignored
redis - redis host:port:db, localhost:6379:0 by default
Examples: dns-crawler-controller domains.txt
dns-crawler-controller domains.txt 192.168.0.22:4444:0
dns-crawler-controller domains.txt redis.foo.bar:7777:2
dns-crawler-controller domains.txt redis.foo.bar # port 6379 and DB 0 will be used if not specified
当您提供大量域名(>1000×CPU核心数)时,控制器进程会使用线程(每个CPU核心4个)来更快地创建作业。
在(更)现代的机器上运行速度要快得多——例如,笔记本电脑中的i7-7600U(带HT)每秒大约处理19k个作业,而服务器上的Xeon X3430(不带HT)仅约处理7k个(两者都使用16个线程,因为它们都显示为4个核心)。
要取消进程,只需发送一个终止信号或在任何时候按Ctrl-C
。进程将执行清理并退出。
dns-crawler-workers
dns-crawler-workers - a process that spawns crawler workers.
Usage: dns-crawler-workers [count] [redis]
count - worker count, 8 workers per CPU core by default
redis - redis host:port:db, localhost:6379:0 by default
Examples: dns-crawler-workers 8
dns-crawler-workers 24 192.168.0.22:4444:0
dns-crawler-workers 16 redis.foo.bar:7777:2
dns-crawler-workers 16 redis.foo.bar # port 6379 and DB 0 will be used if not specified
尝试为每个CPU核心使用超过24个工作者将导致警告(并在实际启动工作者之前开始倒计时)
$ dns-crawler-workers 999
Whoa. You are trying to run 999 workers on 4 CPU cores. It's easy toscale
across multiple machines, if you need to. See README.md for details.
Cancel now (Ctrl-C) or have a fire extinguisher ready.
5 - 4 - 3 -
停止工作与控制器进程相同——按Ctrl-C
(或终止信号)将完成当前作业并退出。
恢复工作
停止工作者不会从Redis中删除作业。因此,如果您停止dns-crawler-workers
进程然后启动一个新的进程(可能是因为要使用不同的工作者数量...),它将获取未完成的作业并继续。
这也可以用来调整工作者数量,如果发现对于您的机器或网络来说太低或太高
- 要减少工作者数量,只需停止
dns-crawler-workers
进程并启动一个新进程,带有新的计数 - 要增加工作者数量,要么使用相同的方法,要么在另一个shell中启动第二个
dns-crawler-workers
进程,工作者数量将相加 - 扩展到多台机器的方式相同,见下文
在多台机器上运行
由于控制器和工作者之间的所有通信都是通过Redis完成的,因此很容易将爬虫扩展到任意数量的机器
machine-1 machine-1
┬───────────────────────────┐ ┬─────────────────────┐
│ dns-crawler-controller │ ------- │ dns-crawler-workers │
│ + │ └─────────────────────┘
│ redis │
│ + │
│ DNS resolver │
└───────────────────────────┘
machine-2
┬─────────────────────┐
------- │ dns-crawler-workers │
└─────────────────────┘
…
…
machine-n
┬─────────────────────┐
_______ │ dns-crawler-workers │
└─────────────────────┘
只需告诉工作者连接到主服务器上的共享Redis,例如。
$ dns-crawler-workers 24 192.168.0.2:6379
^ ^
24 threads redis host
请确保在这些机器上使用与~相同的Python版本运行工作者,否则您将得到不支持的pickle协议
错误。请参阅Python文档中的pickle协议版本。
当然,DNS解析器不需要与dns-crawler-controller
位于同一台机器上——只需在config.yml
中设置其IP即可。爬虫主要使用CZ.NIC的Knot Resolver进行测试,但应与支持DNSSEC的任何合理的解析器兼容。不过,Systemd的systemd-resolved
似乎非常慢。
Redis也是如此,您可以将控制器和工作者指向运行Redis的单独机器(如果您使用Redis做其他事情,除了dns-crawler,不要忘记将其指向一个空DB,它默认为0
)。
更新依赖项
MaxMind在星期二更新GeoIP数据库,因此设置一个cron作业以保持它们更新可能是个好主意。更多关于这一点可以在maxmind.com:GeoIP2的自动更新上找到。
如果您使用多台机器运行工作者,不要忘记在这些机器上更新GeoIP(或者设置一个共享位置,例如通过sshfs或nfs)。
监控
命令行
$ rq info
default |████████████████████ 219458
1 queues, 219458 jobs total
0 workers, 1 queues
Web界面
$ pip install rq-dashboard
$ rq-dashboard
RQ Dashboard version 0.4.0
* Serving Flask app "rq_dashboard.cli" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:9181/ (Press CTRL+C to quit)
测试
一些基本测试位于此仓库中的tests
目录中。如果您想手动运行它们,请查看.gitlab-ci.yml
中的test
阶段作业。基本上它只是下载免费的GeoIP数据库,告诉爬虫使用它们,并爬取一些域名,检查JSON输出中的值。它运行两次测试——首先使用默认的DNS解析器(ODVR),然后使用系统解析器。
如果您正在考虑编写一些额外的测试,请注意,GitLab CI 中使用的某些Docker容器没有配置IPv6(即使主机机器上可以工作),因此检查例如 WEB6_80_www_VENDOR
将会失败,除非进行额外设置。
操作系统支持
爬虫主要针对Linux开发,但它应该可以在Python支持的任何操作系统上工作——至少是工作部分(但如果你在你的操作系统上成功运行Redis服务器,控制器也应该可以工作)。
一个例外是Windows,因为它 不支持 fork()
,但在WSL(Windows Subsystem for Linux)下可以运行。
……因此,您可以很容易地将一台游戏机变成一个互联网爬虫。
错误报告
请在此Gitlab仓库中创建 问题。
项目详情
下载文件
下载适用于您的平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。