到目前为止,我们已经可以编写一些反爬虫机制比较薄弱的网站爬虫了。不过,到上一篇博客结束,我们抓到的数据依然还是存储在文本文件中。如此会存在一些不方便,比如不方便数据查找、删除、更新,可能在第二次抓取的时候重复存储等。这里,介绍一下用数据库存储爬虫数据。
由于爬虫数据的结构变化可能比较频繁(不同时期网页展示内容可能丰富程度不一样),传统的SQL型数据库用来存储的话会比较麻烦,所以我们更多的是使用NoSQL数据库,这里以MongoDB为例。
在数据抓取的过程中,我们就应该考虑好数据的表现形式。在大多数情况下,爬虫抓到的数据为以下两种形式之一:
- JSON文档形式(如前一篇的拉勾招聘信息),这种格式适用于绝大多数我们需要文本内容的情况,我们将自己需要的数据的单条记录表示为一批自定义key对应网页中特定value的k-v pair,或者这样的嵌套关系。
- 文件形式,这种格式适用于抓取图片资源、文档资源等在浏览器端表现为二进制数据或需要下载的链接里面的数据。
首先介绍第一种格式吧。以上一篇博客的拉勾网为例,我们最后抓取到的数据,其实最后可以表达为以下形式(这里为了举例只是展示少量字段):
{
"jobTitle": "Python工程师",
"companyName": "奇虎360科技有限公司",
"salary": "20k-35k",
"city": "北京",
"workYear": "3-5年",
"positionId": 1234566,
}
上面关于JSON格式的描述的最后一句有点绕,具体点的一个例子就是,比如我们抓取某个关键字对应的百度搜索结果,那么,我们得到的数据是这样的(它不只是一个简单的字符串关系的kv对,而是value也可能是一个object,如list或者dict):
{
"keyword": "这是我们搜索的关键字",
"resultCount": "百度搜到了多少条信息",
"items":[ # 我们搜到的信息的集合
{ # 单条信息
"title": "标题",
"abstract": "信息摘要",
"releaseTime": "这条信息发布的时间",
"url": "这条信息的链接",
}, ...
],
}
那么,对于上面拉勾招聘的数据,我们能够肯定的是,这个招聘的id(“positionId”字段)一定不会重复,于是,我们在将其存储到数据库的过程中,可以用positionId字段来唯一确定一条招聘信息,也即是主键。对应到Python代码,存储到MongoDB的代码示例如下:
import datetime
from pymongo import MongoClient
MONGO_CONN = MongoClient(host, port) # MongoClient是线程安全的且内部维护了一个连接池,所以全局使用一个即可
def save(data): # data 表示需要存储的单挑记录(如上面的json,在Python中表现形式为dict)
data['_id'] = data['positionId'] # 理由已经解释
data['updateTime'] = datetime.datetime.now() # 我们最好记录下更新的时间
# 其中,database、collection为你的数据库及集合名称
MONGO_CONN['database']['collection'].update_one(
filter={'_id': data['_id']},
update={'$set': data},
upsert=True
)
我们可以通过如上方式将之前存储到文件的代码修改为存储到数据库中。pymongo是一个第三方库,所以你依然需要通过”pip install pymongo”来安装它。pymongo有很多操作MongoDB的API接口,这里不做过多讲解,你可以阅读它的文档或者在遇到问题的使用通过Google来解决。
对于这样的内容,我们的抓取及存储方式大概可以概括为:
- 选择合适的格式保存
- 按照一定的规则确定不会重复保存两条一样的数据到库中
- 写爬虫,并将存数据的接口实现为数据库存储方式
那么,对于存储为文件的数据抓取,我们怎么做会更好一点呢?
其实还是要分情况考虑问题。如果你只是想简单的抓取一批美女图片(不管你想要健康或者是不健康的^_^),那么直接下载二进制流之后随便取个名字存储就好了,比如随机一串字符,或者使用文件内容的数据摘要(MD5,sha1, …),又或者是一个uuid,只要能确定不重复就够了,然后你就可以色眯眯地盯着这些图片***了。
另一种情况下,比如这个文件是与一些如上所述的JSON数据挂钩的,比如说上面拉勾招聘的公司logo图,这时候怎么考虑存储问题更好呢?
这种情况下,我们要考虑到两个问题:
- 不要重复存储
- 可以方便地找回文件及其表示的含义
要达到这样的目的,我们可以这样:在得到文件的内容后(一般为”requests.get(‘url’).content”的值),我们取它的摘要存储为文件名(md5重复概率比较大,建议使用sha1),然后在MongoDB中维护一张collection——”fileMetaData”,这个集合用于记录对应文件名(sha1)对应的文件的content length、file type、file path等信息。在招聘信息里增加一个字段“logo”,其值为fileMetaData中这条数据对应的id即可(图方便直接用文件的摘要做id)。考虑到另一个问题是,如果文件数量较大(单个文件夹存储数量有限),我们还可以将摘要的前几位截取作为文件夹名。
小结
这里我们大概了解了如何通过数据库保存我们爬到的数据,以达到更方便检索、更新、删除的操作。
有朋友私信说希望更新一部分关于反爬虫机制的处理相关的内容,这部分将会在后续更新,不过在这之前,先会有一篇关于较大数据量的抓取策略的探讨。