爬虫代码改进(三)|生成器与类的使用

422 阅读3分钟
原文链接: zhuanlan.zhihu.com

本文是下面两篇文章的续篇

爬虫基本原理

爬虫代码改进(一)

爬虫代码改进(二)

本系列包括如下内容

  • 抓取豆瓣top250一页多个字段
  • 整合成列表
  • 存储为json文件
  • 定义成函数形式
  • 多页抓取之构造url
  • 多页抓取之翻页
  • 抓取二级页面数据
  • 通过生成器优化代码
  • 改写为类的形式

本文主要讲

  • 通过生成器优化代码
  • 改写为类的形式

通过生成器优化代码

使用生成器可以大大改进代码的可扩展性,比如下面我们将抓取一级页面250个电影信息,翻页方法使用生成器改写为

import requests # 导入网页请求库
from bs4 import BeautifulSoup # 导入网页解析库
import json

def start_requests(url):
    r = requests.get(url)
    return r.content

def parse(text):
    soup = BeautifulSoup(text, 'html.parser')
    movie_list = soup.find_all('div', class_ = 'item')
    for movie in movie_list:
        mydict = {}
        mydict['title'] = movie.find('span', class_ = 'title').text
        mydict['score'] = movie.find('span', class_ = 'rating_num').text
        quote = movie.find('span', class_ = 'inq')
        mydict['quote'] = quote.text if quote else None
        star = movie.find('div', class_ = 'star')
        mydict['comment_num'] = star.find_all('span')[-1].text[:-3]
        yield mydict # 这里使用生成器

def get_all(): # 获取所有页封装成一个函数
    for i in range(2):
        url = 'https://movie.douban.com/top250?start={}&filter='.format(i * 25)
        text = start_requests(url)
        result = parse(text)
        yield from result # 返回一个生成器

yieldyield from没有了解的读者可以先看这篇文章

这样做的好处是扩展性很好,get_all()的结果就是一个包含所有信息的生成器,只要一个循环就可以自由调用所有抓取到的信息。而且生成器的特性是调用时才会运行,所以相当于每次抓取到信息就直接一步到位输出到自己想要的位置,而没有了 中间先存储一步,再提取 的资源浪费。

当你要print时,只需要

def main():
    for info in get_all():
        print(info)
if __name__ == '__main__':
    main()

当你需要存到list,再存成json文件时可以

def main():
    result_list = list(get_all())
    s = json.dumps(result_list, indent = 4, ensure_ascii=False)
    with open('movies.json', 'w', encoding = 'utf-8') as f:
        f.write(s)
if __name__ == '__main__':
    main()

当你需要每一条存储到数据库的时候,可以

def main():
    for info in get_all():
        put_into_database_code
if __name__ == '__main__':
    main()

可以想象如果不使用生成器需要怎么做,有时候要构造一个list,有时候就不要,改一个需求,代码要改的地方很多,这种重新设计是比较低效的。

对于翻页方法和二级页面抓取来说,思路也是类似的,读者可以自己去尝试一下。

改写为类的形式

当需要设置一些全局变量的时候,可以考虑将代码改写为类的形式。下面我们以翻页为例

import requests # 导入网页请求库
from bs4 import BeautifulSoup # 导入网页解析库
import json

class Doubantop(object):

    def __init__(self):
        self.baseurl = 'https://movie.douban.com/top250'
        self.result_list = []

    def start_requests(self, url):
        r = requests.get(url)
        return r.content

    def parse(self, text):
        soup = BeautifulSoup(text, 'html.parser')
        movie_list = soup.find_all('div', class_ = 'item')
        for movie in movie_list:
            mydict = {}
            mydict['title'] = movie.find('span', class_ = 'title').text
            mydict['score'] = movie.find('span', class_ = 'rating_num').text
            quote = movie.find('span', class_ = 'inq')
            mydict['quote'] = quote.text if quote else None 
            star = movie.find('div', class_ = 'star')
            mydict['comment_num'] = star.find_all('span')[-1].text[:-3]
            self.result_list.append(mydict)
        nextpage = soup.find('span', class_ = 'next').a
        if nextpage:
            nexturl = self.baseurl + nextpage['href']
            text = self.start_requests(nexturl)
            self.parse(text)

    def write_json(self, result):
        s = json.dumps(result, indent = 4, ensure_ascii=False)
        with open('movies.json', 'w', encoding = 'utf-8') as f:
            f.write(s)

    def start(self):
        text = self.start_requests(self.baseurl)
        self.parse(text)
        self.write_json(self.result_list)

douban = Doubantop()
douban.start()

这样做的好处是,result_listbaseurl两个变量不是所有代码的全局变量,只是这个类中的全局变量,相当于把数据封装到这里。当代码更加复杂,涉及到多个类的时候,这样组织代码可以分工明确,变量之间不会交叉混乱。

多个类的例子可以看这篇文章感受一下

至此,代码改进系列结束,看完的读者应该可以随心所欲设计代码了。接下来就要开始熟练解析库、将数据存储成各种格式、了解各种反爬策略的学习了。

专栏信息

专栏主页:python编程

专栏目录:目录

爬虫目录:爬虫系列目录

版本说明:软件及包版本说明