前言html
在刚开始学习python的时候,有看到过迭代器和生成器的相关内容,不过当时并未深刻了解,更谈不上使用了,实际上是能够用生成器来改造一下的,因此本次就使用生成器来优化一下爬虫代码python
何时能够使用生成器呢?app
通常爬虫常常会经过for循环来迭代处理数据,例如我以前爬取20页数据时,会先把得到的数据存储到一个列表或字典中,而后再把整个列表或字典 return 出去,而后保存数据至本地又会再调用这个列表获取数据(其实作了2步:先把页面的数据提取出来存到列表,后面用的时候再迭代列表);函数
相似这种直接使用列表或字典来存储数据,实际上是先存储到了内存中,若是数据量过大的话,则会占用大量内存,这样显然是不合适的;学习
此时就能够使用生成器,咱们每提取一条数据,就把该条数据经过 yield 返回出去,好处是不须要提早把全部数据加载到一个列表中,而是有须要的时候才给它生成值返回,没调用这个生成器的时候,它就处于休眠状态等待下一次调用优化
优化爬虫代码url
首先看一下未使用生成器的代码spa
# -*- 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提取,因此数据时存储在一个列表中),能够把它改造为生成器;code
get_all_image_url()函数:调用parse_html()函数,经过控制爬取页码,提取全部页面的全部图片url,而后存到一个列表中返回出去,能够改造为生成器;orm
main()函数:调用get_all_image_url()函数获得全部图片url的列表,而后迭代这个列表,来获得每个图片url来下载图片
接下来要作的就是改造 parse_html()函数 和 get_all_image_url()函数
这个其实也比较简单,只须要把本来要追加到列表中的东西经过 yield 关键字返回出去就好了
parse_html()函数:
''' 遇到问题没人解答?小编建立了一个Python学习交流群:778463939 寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书! ''' 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()函数
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地址
而后上面代码中有个地方须要注意
for i in range(1, depth): 这个for循环,是迭代爬取页码
list_data = parse_html(html):调用parse_html()函数,获取每一页内容的生成器对象
for img in list_data: 迭代 list_data,而后经过yield img 把值返回出去
get_all_image_url()函数 还能够用如下方式返回结果
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循环,能够达到相同的效果(具体含义能够查看 Python yield from 用法详解、yield from)
main()函数 不须要做改动,由于咱们在调用生成器对象时,也是经过for循环来提取里面的值的,因此这部分代码和以前同样
OK,本次代码优化到此结束,python有太多东西要学啦,感受本身懂得仍是太少,要保持学习的心态,加油~