python爬虫开发规范

前言

实现一个爬虫需求很简单。但是管理很多爬虫是有以下难点的:

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.代码流程日志、执行报错日志。代码流程日志让你知道代码执行到什么位置了(有时候也会想看爬虫是否还在跑),执行报错日志记你的爬虫为何报错终止。

image

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

通过这种方式,所有的日志记录全部通过装饰器来控制,开发的时候只需要想清楚哪些代码应该剥离成一个函数,记录它的入参和返回。

装饰器中可控制 日志所在的文件夹名日志名是否记录入参、是否记录返回。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容