今天就直接进入主题
这个爬虫需要用到的包:
其中mongo_cache使我们自己定义的向MongoDB里存储数据的包,大家可以参照我之前的有关MongoDB的讲解https://blog.csdn.net/oyjl19961216/article/details/82778256
import requests
from fake_useragent import UserAgent # 随机生成User-Agent
from retrying import retry # 使用装饰器
import hashlib
import queue
import re
from urllib import robotparser
from urllib.parse import urlparse, urldefrag, urljoin
from threading import Thread
from datetime import datetime
import time
import mongo_cache
第一步我们可以使用面向对象的思想,所以先创建一个类
class CrawlerCommon(object):
"""https://www.csdn.net/
通用下载爬虫
"""
第二步:初始函数,这里有一些函数也是自己定义的,会在下面提及到
def __init__(self, init_url):
"""
初始化函数
:param init_url:
"""
__ua = UserAgent()
# 使用fake-useragent随机生成一个访问头
self.headers = {'User-Agent': __ua.random}
# 设置最初的种子网址
self.seed_url = init_url
# 使用不同的队列会造成BFS和DFS的效果
# 使用先进先出队列产生广度优先搜索,使用先进后出(栈)产生深度优先搜索
self.crawler_queue = queue.Queue()
self.crawler_queue.put(init_url)
self.rp = get_robots(init_url)
self.link_regex = '(index|view)'
self.throttle = Throttle(5.0)
# self.mcache = mongo_cache.MongoCache()
self.visited = {init_url: 0}
第三步:定义下载函数,与重试下载函数
def dowmload(self, url_str, data=None, method='GET', proxies={}):
"""
真正的下载类
代理模式
:param url_str:
:param data:
:param method:
:param proxies:
:return:
"""
print('download url is :::::', url_str)
try:
result = self.retry_download(url_str, data, method, proxies)
except Exception as e: # 异常处理尽量使用具体的异常
print(e)
result = None
return result
@retry(stop_max_attempt_number=3)
def retry_download(self, url_str, data, method, proxies):
"""
通过装饰器封装重试下载模块,最多重试三次
:param url_str: 下载网页的最终地址
:param data: post传输数据
:param method: 下载方法post或get
:param proxies: 代理服务器
:return: 下载结果
"""
if method == 'POST':
result = requests.post(url_str, data=data, headers=self.headers, proxies=proxies)
else:
result = requests.get(url_str, headers=self.headers, timeout=3, proxies=proxies)
assert result.status_code == 200 # 使用断言判断下载状态,成功则返回结果,失败抛出异常
return result.content
因为是完整的爬虫所以不可能只是下载一页:所以会爬取网页上的网址再次进行爬取
def nomalize(self, url_str):
"""
补全下载链接
实现一个类方法的时候,要注意类方法是否使用了当前类的属性或其他的方法,
如果没有使用说明和当前类没有直接联系,最好独立出来,当做工具方法
:param url_str:
:return:
"""
real_url, _ = urldefrag(url_str) # 去掉网址后面的#
return urljoin(self.seed_url, real_url) # 将下载地址拼接上网站前缀
最后进行运行:
def run(self):
"""
进行网页爬取的主要方法
:return:
"""
while not self.crawler_queue.empty():
url_str = self.crawler_queue.get()
# 检测 robots.txt文件规则
if self.rp.can_fetch(self.headers['User-Agent'], url_str):
self.throttle.wait_url(url_str)
depth = self.visited[url_str]
if depth < MAX_DEP:
# 下载链接
html_content = self.dowmload(url_str)
# 存储连接
if html_content:
# self.mcache[url_str] = html_content
save_url(html_content, url_str)
# 筛选出页面所有的连接
url_list = extractor_url_lists(html_content)
# 筛选出需要爬去的连接
filter_urls = [link for link in url_list if re.search('/(52_52542)', link)]
for url in filter_urls:
# 补全链接
real_url = self.nomalize(url)
# 判断连接是否访问过
if real_url not in self.visited:
self.visited[real_url] = depth + 1
self.crawler_queue.put(real_url)
else:
print('robots.txt 禁止下载:', url_str)
但是到这里这个爬虫到这里依然不完整,许多细心的小伙伴肯定发现了,接下来继续补全,下面这些函数是定义在类外的函数,因为他们没有用到类内的变量。
上面调用了检查robots文件的函数:
def get_robots(url):
"""
解析robots.txt文件
:param url:
:return:
"""
rp = robotparser.RobotFileParser()
robots_url = urlparse(url).scheme + '://' + urlparse(url).netloc
rp.set_url(urljoin(robots_url, 'robots.txt'))
rp.read()
# 返回Ture说明可以调用,否则返回False
# rp.can_fetch("http://www.baidu.com",user-agent)
return rp
抽取网页链接的函数:
def extractor_url_lists(html_content):
"""
抽取网页中的其他链接
:param html_content:网页内容
:return:网页中的其他链接
"""
# html.xpath('//a[@href]')
url_regex = re.compile('<a[^>]+href=["\'](.*?)["\']', re.IGNORECASE)
return url_regex.findall(html_content.decode("gbk"))
存储下载内容的函数:
def save_url(html_content, url_str):
"""
存储下载内容
:param html_content:
:param url_str:
:return:
"""
# md5加密
md5 = hashlib.md5()
md5.update(html_content)
file_path = "./download/" + md5.hexdigest() + ".html"
with open(file_path, 'wb') as f:
f.write(html_content)
最后还需要一个下载限速器:
class Throttle(object):
下载限流器
def __init__(self, delay):
self.domains = {} # 可以放到MongoDB数据库中
self.delay = delay # 两次下载时间间隔
def wait_url(self, url_str):
# 以netloc为基础进行休眠
domain_url = urlparse(url_str).netloc
# 从字典里取出上次访问的时间
last_accessed = self.domains.get(domain_url)
if self.delay > 0 and last_accessed is not None:
# 计算当前时间和上次访问时间的间隔,然后被规则间隔减去,
# 如果大于0,说明间隔时间不到,否则直接下载下个网页
# sleep_interval加上随机偏移量,会更加真实
sleep_interval = self.delay - (datetime.now() - last_accessed).seconds
if sleep_interval > 0:
time.sleep(sleep_interval)
self.domains[domain_url] = datetime.now()
最后传入网址就可以爬取了,里面的一些匹配规则根据你所需要的来定,不要盲目抄袭,没有意义。