FOFA爬虫大法——API的简单利用

最近因项目需要,需要爬虫FOFA里面数据进行数据统计分析,因此就有了本篇文章。

环境

描述环境为文章中所用到的所有技术以及中间件并非全部安装,可根据使用的规模进行调整使用。

语言:python2.7(必须)

模块:requests(必须)

操作系统:MacOS 10.13.4(非必须)

分布式消息队列管理:Celery(最后一节安装)

日志记录:logging(必须)

中间价:Redis(最后一节安装)/MySQL(必须)

数据库操作:pymysql(必须)/DBUtils(必须)

安装环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 安装requests

pip isntall requests

# 安装celery Mac

pip install celery

# 安装celery Windows

pip isntall celery=3.1.25

# 安装 pymysql

pip install pymysql

# 安装logging
pip install logging

# 安装 DBUtils 数据库连接池
pip install DBUtils

注:Windows安装Celery时最好安装3.1.25版本,不然可能会有意想不到的惊喜。

FOFA API

文档:https://fofa.so/api

请求:https://fofa.so/api/v1/search/all

请求方式:GET

请求参数:

参数名称 参数类型 参数描述 是否必须
email string FOFA pro账号邮箱
key string FOFA pro账户登陆key
qbase64 string FOFA查询语句base64编码
page int 页码,默认为第一页
size int 每页数量,默认100条
fields string 字段段列表,默认为host,用逗号分隔多个参数,如(fields=ip,title),可选的列表有:host title ip domain port country city

响应:

参数名称 参数类型 参数描述
mode string 查询模式
page int 当前页码
size int 请求返回结果的总数
results array 请求返回结果的详情数组

数据库设计

设计数据库存放FOFA爬虫数据,方便统计查询。

字段名称 字段类型 是否允许为空 描述
id int(11) id自增
host varchar(255) host
ip varchar(255) ip地址
port varchar(255) 端口号
protocol varchar(255) 协议
country_ name varchar(255) 国家名称
region_name varchar(255) 省份名称
city_name varchar(255) 城市名称
isp varchar(255) 运营商
fofa_sql text FOFA查询语句
create_date datetime 创建时间
update_date datetime 更新时间

sql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DROP TABLE IF EXISTS `fofa_spider`;

CREATE TABLE `fofa_spider` (
`id` INT (11) NOT NULL AUTO_INCREMENT,
`host` VARCHAR (255) NOT NULL,
`ip` VARCHAR (255) NOT NULL,
`port` VARCHAR (255) DEFAULT NULL,
`protocol` VARCHAR (255) NOT NULL,
`country_name` VARCHAR (255) DEFAULT NULL,
`region_name` VARCHAR (255) DEFAULT NULL,
`city_name` VARCHAR (255) DEFAULT NULL,
`fofa_sql` text NOT NULL,
`create_date` DATETIME NOT NULL,
`update_date` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;

数据库sql文件:https://github.com/0nise/scripts/blob/master/fofa_spider.sql

小试牛刀

本节主要讲解可适用与一般的FOFA爬虫,如果需要大批量数据爬虫请您接着往下看。

环境

语言:python2.7

中间件:MySQL

第三方包:pymysql/requests/

场景:小规模爬虫/一般爬虫

通过查看FOFA API可以得知请求地址和参数,开局一句话功能全靠编。

请求中心

在发送大量的http请求时最好使用统一的HTTP请求中心,方便控制,代码重复利用,提高效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
session = requests.session()
# 请求头
headers = {
'Upgrade-Insecure-Requests': '1',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}
'''
请求中心,控制程序所有HTTP请求,如果请求发生错误进行尝试再次连接
@param url 请求连接
@return 请求响应结果
'''
def fofa_requests(url):
rs_content = ''
while True:
try:
rs = session.get(api_url, verify=False,headers=headers)
rs_text = rs.text
results = json.loads(rs_text)
total_size = results['size']
error = results
if results['error'] and 'None' not in results['error']:
info = u'fofa 错误:'+results['error']+u' 休眠30s'
logging.error(info)
time.sleep(30)
else:
rs_content = results
except Exception as e:
logging.error(u'fofa 错误:'+str(e.message)+u' 休眠30s')
traceback.print_exc()
time.sleep(30)
return rs_content

数据库存储

有了统一的请求中心接下来就该编写入库代码,将爬虫结果存入数据库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'''
批量数据存入数据库
@param results
@param page_no 当前页数
@param page_total 总页数
'''
def batch_insert_db(results,page_no,page_total,fofa_sql):
try:
Z = []
for result in results:
a = (str(result[0]),str(result[1]),str(result[2]),str(result[3]),str(result[4]),str(result[5]),str(result[6]),pymysql.escape_string(fofa_sql))
Z.append(a)
sql = "INSERT IGNORE INTO fofa_spider(id,host,ip,port,protocol,country_name,region_name,city_name,fofa_sql,create_date,update_date) VALUES(DEFAULT,%s,%s,%s,%s,%s,%s,%s,%s,NOW(),NOW())"
cursor.executemany(sql, Z)
connection.commit()
logging.info(u'存入数据库ok,总数量为:'+str(len(Z))+u', page--> '+str(page_no)+'/'+str(page_total))
except Exception as e:
logging.error(u"存入数据库错误,错误信息:"+e.message)
traceback.print_exc()

核心业务代码

可以存入数据库中就该写核心的函数逻辑函数,输入参数仅为FOFA检索语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'''
fofa 爬虫主函数
@param fofa_sql fofa查询语句
'''
def main(fofa_sql):
base64_str = base64.b64encode(fofa_sql)
fields_str = ','.join(fields)
api_url = 'http://fofa.so/api/v1/search/all?email='+fofa_name+'&key='+fofa_key+'&fields='+fields_str+'&size='+str(page_size)+'&page='+str(page_start)+'&qbase64='+base64_str
rs = fofa_requests(api_url)
total_size = rs['size']
# 计算页数
page_end = total_size / page_size + 1 if total_size % page_size != 0 else total_size / page_size
# 存入u 数据库
batch_insert_db(rs['results'],page_start,page_end,fofa_sql)
for page_no in range(1,page_end+1):
api_url = 'http://fofa.so/api/v1/search/all?email='+fofa_name+'&key='+fofa_key+'&fields='+fields_str+'&size='+str(page_size)+'&page='+str(page_no)+'&qbase64='+base64_str
batch_insert_db(rs['results'],page_start,page_end,fofa_sql)

程序运行结果:

运行结果

完整代码地址:https://github.com/0nise/scripts/blob/master/fofa_spider.py

注:运行脚本之前先配置相关配置信息(数据库/FOFA信息)

进阶

问题

针对一般的数据爬虫数据爬虫,上述方法可以完美适应。但如果需要爬虫的是为千万级别规模的数据上述方法就显得有点不行了,解决方案有一般有多线程/多进程/协程等。

思路

针对大规模数据爬虫,很多人想到的是多线程/多进程/协程等方案,但是这些方案的可扩展并不是很强,如果需要调整工具需要停止程序修改程序等,这里我的思路是使用生产者和消费的思路来处理。只需要对上述的代码做轻微修改就可以完美的适应大规模数据爬虫,这里我使用redis+celery的方式来实现。

Redis

Redis是一款开源的、高性能的键-值存储(key-value store)。它常被称作是一款数据结构服务器(data structure server)。

Redis的键值可以包括字符串(strings)类型,同时它还包括哈希(hashes)、列表(lists)、集合(sets)和 有序集合(sorted sets)等数据类型。 对于这些数据类型,你可以执行原子操作。例如:对字符串进行附加操作(append);递增哈希中的值;向列表中增加元素;计算集合的交集、并集与差集等。

为了获得优异的性能,Redis采用了内存中(in-memory)数据集(dataset)的方式。同时,Redis支持数据的持久化,你可以每隔一段时间将数据集转存到磁盘上(snapshot),或者在日志尾部追加每一条操作命令(append only file,aof)。

Redis同样支持主从复制(master-slave replication),并且具有非常快速的非阻塞首次同步( non-blocking first synchronization)、网络断开自动重连等功能。同时Redis还具有其它一些特性,其中包括简单的事物支持、发布订阅 ( pub/sub)、管道(pipeline)和虚拟内存(vm)等 。
Redis具有丰富的客户端,支持现阶段流行的大多数编程语言。

celery

简介

Celery(芹菜)是一个简单、灵活且可靠的,处理大量消息的分布式系统,并且提供维护这样一个系统的必需工具。

任务队列

任务队列是一种在线程或机器间分发任务的机制。

消息队列

消息队列的输入是工作的一个单元,称为任务,独立的职程(Worker)进程持续监视队列中是否有需要处理的新任务。

Celery 用消息通信,通常使用中间人(Broker)在客户端和职程间斡旋。这个过程从客户端向队列添加消息开始,之后中间人把消息派送给职程,职程对消息进行处理。如下图所示:
1018261-20161017182409279-50086417
Celery 系统可包含多个职程和中间人,以此获得高可用性和横向扩展能力。

架构

Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。

消息中间件

Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成,包括,RabbitMQ,Redis,MongoDB等,这里我先去了解RabbitMQ,Redis

任务执行单元

Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中

任务结果存储

Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括Redis,MongoDB,Django ORM,AMQP等,这里我先不去看它是如何存储的,就先选用Redis来存储任务执行结果。

官方文档:http://docs.celeryproject.org/en/latest/
中文笔记:http://www.cnblogs.com/forward-wang/p/5970806.html

修改

上面看不懂没关系,会用就行。

添加celery配置信息

1
2
3
4
5
6
7
8
'''
Celery
'''
from celery import platforms,Celery
platforms.C_FORCE_ROOT = True
# Redis连接地址,如果为本机不需要做修改
broker = 'redis://127.0.0.1:6379/0'
app = Celery('fofa_spider',broker=broker)

添加核心函数

1
2
3
4
5
6
7
8
9
'''
celery 爬虫
@param api_url 爬虫URL
@param fofa_sql FOFA语句
'''
@app.task
def celery_spider(api_url,fofa_sql):
rs = fofa_requests(api_url)
batch_insert_db(rs['results'],fofa_sql)

修改业务逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'''
fofa 爬虫主函数
@param fofa_sql fofa查询语句
'''
def main(fofa_sql):
base64_str = base64.b64encode(fofa_sql)
fields_str = ','.join(fields)
api_url = 'http://fofa.so/api/v1/search/all?email='+fofa_name+'&key='+fofa_key+'&fields='+fields_str+'&size='+str(page_size)+'&page='+str(page_start)+'&qbase64='+base64_str
rs = fofa_requests(api_url)
total_size = rs['size']
# 计算页数
page_end = total_size / page_size + 1 if total_size % page_size != 0 else total_size / page_size
# 存入数据库
batch_insert_db(rs['results'],fofa_sql)
for page_no in range(1,page_end+1):
api_url = 'http://fofa.so/api/v1/search/all?email='+fofa_name+'&key='+fofa_key+'&fields='+fields_str+'&size='+str(page_size)+'&page='+str(page_no)+'&qbase64='+base64_str
logging.info('send task -->'+api_url)
celery_spider.delay(api_url,fofa_sql)

完整代码地址:https://github.com/0nise/scripts/blob/master/fofa_spider_ext.py

运行

  • 运行python fofa_spider_ext.py发送需要爬虫的任务信息
  • 运行celery -A fofa_spider_ext worker -l info 进行消费爬虫

运行成功

celery运行成功

数据库信息

数据库存入数据

参考