前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python爬虫代码优化:使用生成器重构提取数据方法

python爬虫代码优化:使用生成器重构提取数据方法

作者头像
冰霜
发布2022-03-15 16:00:04
5510
发布2022-03-15 16:00:04
举报

前言

在刚开始学习python的时候,有看到过迭代器生成器的相关内容,不过当时并未深入了解,更谈不上使用了

前两天在网上冲浪时,又看到了几篇关于生成器的文章,想到之前写的爬虫代码,其实是可以用生成器来改造一下的,所以本次就使用生成器来优化一下爬虫代码

关于python迭代器、生成器的知识,推荐几篇文章:

  1. https://foofish.net/what-is-python-generator.html
  2. https://pyzh.readthedocs.io/en/latest/the-python-yield-keyword-explained.html#id8
  3. https://zhuanlan.zhihu.com/p/34324811
  4. https://www.jianshu.com/p/87da832730f5

看完上述文章后,我们可以了解到:

  • 生成器函数与普通函数的区别是,生成器用关键字 yield 来返回值,而普通函数用 return 一次性返回值;
  • 当你调用生成器函数的时候,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象
  • 一般使用for循环迭代生成器对象来获取具体的返回值

什么时候使用生成器?

一般爬虫经常会通过for循环来迭代处理数据,例如我之前爬取20页数据时,会先把获得的数据存储到一个列表或字典中,

然后再把整个列表或字典 return 出去,然后保存数据至本地又会再调用这个列表获取数据(其实做了2步:先把页面的数据提取出来存到列表,后面用的时候再迭代列表);

类似这种直接使用列表或字典来存储数据,其实是先存储到了内存中,如果数据量过大的话,则会占用大量内存,这样显然是不合适的;

此时就可以使用生成器,我们每提取一条数据,就把该条数据通过 yield 返回出去,

好处是不需要提前把所有数据加载到一个列表中,而是有需要的时候才给它生成值返回,没调用这个生成器的时候,它就处于休眠状态等待下一次调用

优化爬虫代码

首先看一下未使用生成器的代码

代码语言:javascript
复制
# -*- coding:utf-8 -*-
import requests
from requests.exceptions import RequestException
import os, time
from lxml import etree


def get_html(url):
    """获取页面内容"""
    response = requests.get(url, timeout=15)
    # print(response.status_code)
    try:
        if response.status_code == 200:

            # print(response.text)
            return response.text
        else:
             return None
    except RequestException:
        print("请求失败")
        # return None

def parse_html(html_text):
    """解析一个结果页的内容,提取图片url"""
    html = etree.HTML(html_text)

    if len(html) > 0:
        img_src = html.xpath("//img[@class='photothumb lazy']/@data-original")  # 提取图片url,通过xpath提取会生成一个列表
        # print(img_src)
        return img_src  # 将提取出来的图片url列表返回出去

    else:
        print("解析页面元素失败")

def get_all_image_url(depth):
    """
    提取所有页面的所有图片url
    :param depth: 爬取页码
    :return:
    """
    base_url = 'https://imgbin.com/free-png/naruto/'  # 定义初始url
    image_urls = []
    for i in range(1, depth):
        url = base_url + str(i)  # 根据页码遍历请求url
        html = get_html(url)  # 解析每个页面的内容
        # print(html)
        if html:
            list_data = parse_html(html)  # 提取页面中的图片url
            for img in list_data:
                image_urls.append(img)
    return image_urls

def get_image_content(url):
    """请求图片url,返回二进制内容"""
    try:
        r = requests.get(url, timeout=15)
        if r.status_code == 200:
            return r.content
        return None
    except RequestException:
        return None

def main(depth=None):
    """
    主函数,下载图片
    :param depth: 爬取页码
    :return:
    """
    j = 1
    img_urls = get_all_image_url(depth)  # 提取页面中的图片url
    root_dir = os.path.dirname(os.path.abspath('.'))
    save_path = root_dir + '/pics/'  # 定义保存路径
    # print(img_urls)
    # print(next(img_urls))
    # print(next(img_urls))
    for img_url in img_urls:  # 遍历每个图片url
        try:
            file_path = '{0}{1}.{2}'.format(save_path, str(j), 'jpg')
            if not os.path.exists(file_path):  # 判断是否存在文件,不存在则爬取
                with open(file_path, 'wb') as f:
                    f.write(get_image_content(img_url))
                    f.close()
                    print('第{}个文件保存成功'.format(j))
            else:
                print("第{}个文件已存在".format(j))
            j = j + 1
        except FileNotFoundError as  e:
            print("遇到错误:", e)
            continue

        except TypeError as f:
            print("遇到错误:", f)
            continue


if __name__ == '__main__':
    start = time.time()
    main(2)
    end = time.time()
    print(end-start)

parse_html()函数:它的作用解析一个结果页的内容,提取一页的所有图片url(通过xpath提取,所以数据时存储在一个列表中),可以把它改造为生成器;

get_all_image_url()函数:调用parse_html()函数,通过控制爬取页码,提取所有页面的所有图片url,然后存到一个列表中返回出去,可以改造为生成器;

main()函数:调用get_all_image_url()函数得到所有图片url的列表,然后迭代这个列表,来得到每一个图片url来下载图片

接下来要做的就是改造 parse_html()函数get_all_image_url()函数

这个其实也比较简单,只需要把原本要追加到列表中的东西通过 yield 关键字返回出去就行了

parse_html()函数:

代码语言:javascript
复制
def parse_html(html_text):
    """解析一个结果页的内容,提取图片url"""
    html = etree.HTML(html_text)

    if len(html) > 0:
        img_src = html.xpath("//img[@class='photothumb lazy']/@data-original")
        # print(img_src)
        for item in img_src:
            yield item

get_all_image_url()函数:

代码语言:javascript
复制
def get_all_image_url(depth):
    """
     提取所有页面的所有图片url
    :param depth: 爬取页码
    :return:
    """
    base_url = 'https://imgbin.com/free-png/naruto/'  # 定义初始url

    for i in range(1, depth):
        url = base_url + str(i)  # 根据页码遍历请求url
        html = get_html(url)  # 解析每个页面的内容
        # print(html)
        if html:
            list_data = parse_html(html)  # 提取页面中的图片url
            for img in list_data:
                yield img  # 通过yield关键字返回每个图片的url地址

然后上面代码中有个地方需要注意

1.for i in range(1, depth):这个for循环,是迭代爬取页码

2. list_data = parse_html(html):调用parse_html()函数,获取每一页内容的生成器对象

3. for img in list_data: 迭代 list_data,然后通过yield img 把值返回出去

此外,get_all_image_url()函数,还可以用以下方式返回结果

代码语言:javascript
复制
def get_all_image_url(depth):
    """
     提取所有页面的所有图片url
    :param depth: 爬取页码
    :return:
    """
    base_url = 'https://imgbin.com/free-png/naruto/'  # 定义初始url

    for i in range(1, depth):
        url = base_url + str(i)  # 根据页码遍历请求url
        html = get_html(url)  # 解析每个页面的内容
        # print(html)
        if html:
            list_data = parse_html(html)  # 提取页面中的图片url
            yield from list_data

使用关键字 yield from 替代了之前的内层for循环,可以达到相同的效果;

main()函数 不需要作改动,因为我们在调用生成器对象时,也是通过for循环来提取里面的值的,所以这部分代码和之前一样

OK,本次代码优化到此结束,python有太多东西要学啦,感觉自己懂得还是太少,保持学习的心态,加油~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 冰霜blog 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档