在使用爬虫爬取网页信息的时候,如果只爬取固定的网页还好,但是如果从一个网页的源码中解析出其他的链接,爬虫爬取到其他的网页,这样就会出现一个问题,如何确定这个网页我爬取过,如何设置爬取的网页不再爬取。本文地址
本篇文章实现避免重复爬取的思路是:将爬取过的网页的链接和该网页的信息以键值对的形式保存到数据库中,当爬虫爬取一个网页之前,先从数据库中查找是否有该网页的爬取记录,如果有该网页的爬取记录,那么就说明该网页已爬取过,就不需要再爬取该网页了,否则就证明这个网页没有爬取过,继而爬取这个网页的内容。本文地址
使用Mongodb数据库
因为Mongodb数据库是文档型数据库,因此在实现避免重复爬取的机制中我们可以将网页的链接作为key,将网页爬取的内容作为value存储到数据库中,每一个链接对应该链接爬取的内容,这样在爬取一个新的链接就能从数据库中查找该链接是否已经爬取过。因此在这里需要使用Mongodb数据库来保存爬取的链接与爬取的内容。本文地址
python连接Mongodb数据库需要用到pymongo,没有安装可以使用pip install pymongo安装。Mongodb安装也很简单,这里我就不介绍Mongodb数据库的安装方法了。本文地址
代码思路:
- 初始化设置各种参数,连接数据库
- 重写
__setitem__
方法将爬取的链接与内容以键值对的形式存储到数据库 - 重写
__getitem__
方法,用来查询爬取的链接对应的内容 - 重写
__contains__
,用来判断数据库中是否存在需要爬取的链接 - 删除数据库,清除数据。
from pymongo import MongoClient
from datetime import datetime,timedelta
from bson.binary import Binary
import pickle,zlib
class MongoCache:
def __init__(self,expires=timedelta(days=30)):
"""
初始化,设置各种基本参数
:param expires: 设置过期时间
"""
self.client = MongoClient('localhost',27017)
self.db = self.client.practice
self.collection = self.db.htmlpage
# 创建索引
self.collection.create_index('timestamp',expireAfterSeconds=expires.total_seconds())
def __setitem__(self, key, value):
"""
将爬取的内容序列化压缩并以binary格式存储到数据库中
:param key: 爬取的链接
:param value: 对应链接爬取的结果
:return:
"""
record = {'result':Binary(zlib.compress(pickle.dumps(value))),'timestamp':datetime.utcnow()}
self.collection.update({"_id":key},{'$set':record},upsert=True)
def __getitem__(self, item):
"""
通过链接获取到对应链接爬取的结果
:param item: 链接地址
:return: 解压缩并反序列化后的结果
"""
record = self.collection.find({"_id":item})
if record:
return zlib.decompress(pickle.loads(record['result']))
else:
raise KeyError(item + 'does not exists')
def __contains__(self, item):
"""
判断数据库中是否存有该链接地址的信息,如果没有则返回False
:param item:
:return:
"""
try:
self[item]
except KeyError:
return False
else:
return True
def clear(self):
self.collection.drop()
这里只定义了避免网页重复爬取的接口,那么我们可以使用上次爬取糗事百科的代码来调用这个接口,实现避免网页重复爬取。本文地址
import requests
import lxml.html
from practice import MongoCache
class QiuBaiSpider():
def __init__(self):
self.base_url = 'https://www.qiushibaike.com/8hr/page/{}/'
def make_download_list(self,page):
return [self.base_url.format(i) for i in range(page[0],page[1]+1)]
def download_content(self,url):
result = requests.get(url)
html = lxml.html.fromstring(result.text)
content = html.xpath('//div[@class="content"]/span/text()')
return content
def run(self,page):
# 生成下载列表
download_list = self.make_download_list(page)
mongo_cache = MongoCache()
# 查询已有文档
result = mongo_cache.collection.find()
exist_id = []
# 查询到的是结果集,只有每个结果才有_id属性
for url in result:
# 将所有已爬取过的网页链接放在临时列表中,也就是_id字段
exist_id.append(url["_id"])
for download_url in download_list:
# 如果该链接在数据库中存在,说明已爬取过,将不再爬取
if download_url not in exist_id:
content = self.download_content(download_url)
print(content)
mongo_cache[download_url] = content
print('爬取结束')
if __name__ == '__main__':
qiubai = QiuBaiSpider()
qiubai.run([1,12])
因为这次我们将数据保存到了Mongodb数据库中,因此保存到文件以及生成对应页码的文件名代码就没有用了,这里就没写。这里可以试验一下,第一次爬取1-5页的数据,第二次爬取1-8页的数据,第二次爬取由于1-5页的数据在数据库中已经存在,所以爬虫将不再爬取前五页的数据,而是直接爬取6-8页。本文地址