前言
实现一个爬虫需求很简单。但是管理很多爬虫是有以下难点的:
1.在其爬取的网页结构改变的时候。及时发现,且不因某一字段而影响整体数据获取
2.爬虫代码逻辑清晰,易于开发人员修改。特别是在开发人员变动的情况下
3.爬虫后台运行失败时能够查询到详细的日志,定位问题
4.只有将业务逻辑和代码逻辑分离开,才能提升开发效率和代码质量。特别是日志记录方面
常用库
# 报错重试
from retry import retry
# 请求
import requests
# 解析
from lxml import etree
import json
import re
爬虫模板
父类工具类
一些爬虫可以复用的函数,如:解析xml中的数据,解析字典中的数据,数据存储
class Spider(object):
def _parse_xml_data(self, data_xml, xpath_str):
try:
use = data_xml.xpath(xpath_str)[0].strip()
return use
except:
return ''
def _parse_dict_data(self, data_dict, *args):
# 解决字典中不存在key的情况,适用于可以查询不到的情况
try:
use = ''
for i in args:
use = data_dict[i]
data_dict = use
return use
except:
return ''
def _save_data(self, total_list):
pass
子类爬虫类
实现某一网站的爬虫,建议一个网站一个文件,实现一个类,类中有该网站各数据的对应爬取函数。
platform_name = 'game'
class GameSpider(Spider):
def __init__(self):
super().__init__()
self.headers = {}
@log()
def main(self):
@log()
@retry(tries=3, delay=5)
def __get_data_list(url):
res = requests.get(url, headers=self.headers)
comments_list = []
if comments_list:
return comments_list
else:
raise ValueError(res.text)
@log()
def __parse_data(data):
return temp_dict
url_list = []
total_list = []
for url in url_list:
try:
data_list = __get_data_list(url = url)
except:
continue
for data in data_list:
temp_dict = __parse_data(data = data)
total_list.append(temp_dict)
self._save_data(total_list)
爬虫步骤
爬虫基本上就分为3个步骤: 请求获取到有效数据=>数据解析=>数据存储
1.请求获取到有效数据
请求外部网址的痛点是,请求网址可能会失败需要重试,所以将请求外部网址+获取数据列表剥离出来形成一个函数,用retry装饰器来实现重试,避免突然的网络问题导致的获取数据失败。
任务文件
try:
data_list = self.__get_data_list(url)
except:
continue
请求外部网址+获取数据列表
# html版
@log()
@retry(tries=3, delay=8)
def __get_data_list(url):
res = requests.get(url, headers=self.headers)
use = etree.HTML(res.text)
comments_list = use.xpath('//li')
if comments_list:
return comments_list
else:
raise ValueError(res.text)
# json版
@log()
@retry(tries=3, delay=8)
def __get_data_list(url):
res = requests.get(url, headers=self.headers)
data = json.loads(res.text)
if 'viewpoints' in data.keys() and data['viewpoints']:
return data['viewpoints']
else:
raise ValueError(res.text)
2.数据解析
# html版
@log()
def __parse_data(self, xml_data):
player = self._parse_xml_data(xml_data, 'div[@class="detail"]/p[@class="from"]/span[@class="name"]/text()')
content = self._parse_xml_data(xml_data, 'div[@class="detail"]/p[@class="text"]/text()')
temp_dict = {
'用户名': player,
'评论': content,
}
return temp_dict
# json版
@log()
def __parse_data(self, data):
data = {
'info':{
'name': 'Tom'
}
'list':[
{'socre': 99}
]
}
temp_dict = {
'name': self._get_dict_data(data, 'info', 'name')
'score': self._get_dict_data(data, 'list', 0, 'score')
}
return temp_dict
3.数据存储
数据存储一般是比较通用的,可以写在父类工具类中
日志记录
日志记录如果不规范,代码的各处会散步着log.info****、log.error(代码混乱),写代码的时候需要想哪些该记哪些不该记,没有工程化这些思路。加上爬虫文件多了的话找日志所在的位置也十分痛苦。
需要考虑2点:
1.多爬虫文件、多函数。这涉及到日志文件的管理。
最终是决定以 爬取平台 为文件夹名 ,日期+执行函数 为日志名
2.代码流程日志、执行报错日志。代码流程日志让你知道代码执行到什么位置了(有时候也会想看爬虫是否还在跑),执行报错日志记你的爬虫为何报错终止。
2021-11-12crawl_comments_and_user_im.log ,记录代码流程
2021-11-12taptap_error.log,记录代码主函数报错
2021-11-12taptap.log,记录一些共用函数的代码流程(即一个子函数可能多个主函数都在用)
def log_main_func(platform_name):
"""
记录导致主函数(某个爬虫类的主任务函数)终止的报错日志
:param platform_name: 爬取的平台名称
:return:
"""
pass
def log_son_func(platform_name, main_func_name, input_flag=True, output_flag=True):
"""
记录子函数的日志
:param platform_name: 爬取的平台名称
:param main_func_name: 主函数名称(如果是公用子函数main_func_name就填platform_name)
:param input_flag: 是否记录入参
:param output_flag: 是否记录返回
:return:
"""
pass
通过这种方式,所有的日志记录全部通过装饰器来控制,开发的时候只需要想清楚哪些代码应该剥离成一个函数,记录它的入参和返回。
装饰器中可控制 日志所在的文件夹名、日志名、是否记录入参、是否记录返回。