本文主要是介绍Python初识——Scrapy抓取二次元小姐姐图片,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
写在前面
最近在学习Python这门语言,禀着实践是最好的学习方法的原则,上来就迫不及待的学习了Scrapy框架,并结合网上的例子进行了实验,不得不说感觉到了Python和Scrapy的强大之处,同时也感觉算是站在Python的门外,窥得厅堂里面的东西,还是有些小激动的。那么本帖算是对往日工作的一个总结,也希望自己能够把总结这个习惯坚持下去,一方面帮助自己,一方面帮助他人。
参考链接
在此还是很感谢网上许许多多大神们的帖子和经验,这里先列出学习过程中参考的链接,希望可以帮助更多的人。
Scrapy tutorial:https://doc.scrapy.org/en/latest/intro/tutorial.html 很有用的官方教程,建议初学者看一下
知乎参考程序:https://www.zhihu.com/question/27621722/answer/269085034 个人感觉还是很有意思的,程序理解起来也不难
Python3语句变为Python2:http://blog.csdn.net/whatday/article/details/54710403 这个博客讲了urllib库在Python2和Python3中的区别,想用Python2的话就把里面对应的语句改一下就可以了(亲测好用)。
Scrapy浅析
首先奉上整个框架的框架图:
可以看到,Scrapy主要包含一下几个方面的组件:
Scrapy Engine(引擎):主要用来处理整个系统的数据流,触发事务。
Scheduler(调度器):主要接收引擎发过来的请求,途中的数据流表示爬虫(spider)将请求给引擎,引擎再给调度器。这个调度器可以看成是一个带有去除重复URL的队列,由他决定下一时刻要去抓取哪个网页,同时去除重复的网址。
Downloader(下载器):由图可以看到,Downloader用于接收Scheduler的请求,之后就开始对请求的网页进行下载,下载后的网页将储存在Response中给spider。
Spider(爬虫):这部分就是我们需要重点对待的部分了,也是程序的主题部分。当spider收到回复之后,就可以使用Scrapy的Selector进行感兴趣内容的抓取,同时还可以生成下次要抓取的页面通过请求的方式给引擎。
ItemPipeline(项目管道):Item是整个抓取过程的实体,也就是我们所需要的资源,个人认为一般就是我们需要的链接,例如图片,视频等的URL;而Pipeline接到这样的实体之后,就可以对实体中的内容进行处理,例如下载图片和音频等工作,这部分也是我们需要编程的部分。
Middlewares(中间件):各种中间件,从图中来看应该是用于数据的流向,具体的功能还没有研究到。
再来大致讲一下整个框架的运行流程:
1. 爬虫将URL以请求(Request)的形式发送给引擎,引擎给予调度器。
2. 引擎从调度器中取出将要抓取的URL(更确切的说是请求),发送给下载器,下载器对该URL进行下载。
3. 下载器将下载的内容以回复(Response)的方式给引擎,引擎转送给爬虫。
4. 爬虫对回复进行解析,将解析出的实体(Item)通过引擎给予管道(Pipeline),将解析出的URL(下一次要抓取的网页)通过引擎给调度器。
5. 管道接收到实体之后对其中的资源进行下载。
Scrapy抓取http://www.acg.fi/
作为一个来自二次元的程序员,这个网站着实让人眼前一亮,毕竟之前只知道B站:D
废话不多说,直接说如何进行该网站的抓取。
首先用浏览器打开要抓取的网页acg.fi,随后我们查看他的源代码,发现这个网站的源代码真心的乱啊,一点儿都不归正:|不过没有关系,我们可以用检查的模式进行代码的浏览,很快就能找到我们想要解析的地方如下图所示
可以看到我们主要要解析的标签为<li>下的<a>标签,通过总结我们发现<li>标签下的id属性里都有“menu-item”这样的字段,很方便的就可以区分与其他的<li>标签,这时候我们就可以编写代码了,这里主要用xpath的方法,程序也很简单,rlt = response.xpath("//li[contains(@id,'menu-item')]//a/@href").extract()就可以提取到上面的七个板块的URL了,但是请注意并不是所有的版块都是有图的,比如上面的信息页和大事记等,因此还要进行列表(list)的切片操作,随后把URL封装成Request的形式发送出去就好了。这里还是强烈建议新手多看看scrapy的tutorial里面关于Selector的讲解,很有受用(链接:https://doc.scrapy.org/en/latest/topics/selectors.html)
如果这个地方没有了问题,那么解析网页基本上就没啥大问题了,可以看到后面的操作基本上都是这么找标签,然后提取。爬虫部分的代码也就可以贴上来了:D
spider代码
#!/usr/bin/env python
# coding:utf-8from acg.items import AcgItem;
from acg import settings;
import os;
import scrapy;
import urllib.request;
import urllib.parse;class acgspiders(scrapy.Spider):''' 爬取一个网站 '''name = 'images';start_urls = ["http://www.acg.fi",];count = 0;page = 1;''' 三级爬取 '''''' 该部分主要获取要取得的内容 '''def parse3(self, response):# 判断已经获取了多少张图片了# 如果获取了够3000张图片了,就返回if self.count >= 3000:return None;# 先获取itemitem = response.meta["item"];# 抓取图片的urlimage_url = response.xpath("//article[@class='article-content']//img/@src").extract();# 打印一共有多少个图片urlprint("一共找到图片%d张" % len(image_url));# 进行图片的抓取for url in image_url:url = urllib.request.quote(url,safe='/:?=.!');if "jpg" in url:self.count += 1;item["img_url"] = url;item["img_name"] = "img"+str(self.count);yield item;if "png" in url:self.count += 1;item["img_url"] = url;item["img_name"] = "img"+str(self.count);yield item;''' 二级爬取 '''''' 该爬取主要是将各栏目中的页面的url给爬取出来 '''def parse2(self, response):# 先获取itemitem = response.meta["item"];# 爬取该栏目下的分页情况pages_url = response.xpath("//div[@class='fenye']//a/@href").extract();# 这个num主要是为了产生各下级页面的urlnum = 1;# 先判断该栏目是否只有一个页面if len(pages_url)==0:# 如果是的话,把该栏目该页的url作为iterator的内容给parse3page = response.xpath("//h1[@class='article-title']//a/@href").extract_first();yield scrapy.Request(page, meta={"item":item}, callback=self.parse3);else:# 如果该栏目下有多页的话,则把各个页面的url取出来# 这里一定要进行切片操作,因为每次进来的都是第一页,而下面总会有一个# 按钮名曰“下一页”,这里就是要舍弃这个下一页for page in pages_url[:len(pages_url)-1]:# 对该页面进行请求yield scrapy.Request(page, meta={"item":item}, callback=self.parse3);# 下级页面自加num+=1;# 打印当前自己爬取了多少界面print(">>> [parse2] -> 该栏目一共有%d个分页" % num);''' 一级爬取 '''''' 该爬取主要找到当前界面下有多少个栏目(二级界面) '''def parse1(self, response):# 首先获取item的值item = response.meta["item"];# 爬取该网站下的二级界面pages_url = response.xpath("//div[@class='card-item']//h3//a/@href").extract();# 打印出来找到了多少个二级页面print(">>> [parse1] -> %s栏目的第%d页一共有%d个卡片" % (item["dir_name"],int(item["page_num"]),len(pages_url)));# 生成generatorfor url in pages_url:yield scrapy.Request(url,meta={"item":item}, callback=self.parse2);# 爬到网页数自加,由于网页数量可能比较多,因此不希望无限制的抓取下去if item["page_num"] < 20:item["page_num"]=item["page_num"]+1;print(">>> [parse1] -> item's page_num is : %d" % item["page_num"]);# 找寻下个一级页面next_url = item["lm_url"]+"/page/"+str(item["page_num"]);# 这里只生成一个generatoryield scrapy.Request(next_url,meta={"item":item},callback=self.parse1);''' 初级爬取 '''''' 该函数主要找到该网站下的各个分栏,之后给parse1 '''def parse(self, response):# 抓取分栏# 要拿到名称和链接lm_name = response.xpath("//li[contains(@id,'menu-item')]//a/text()").extract();lm_url = response.xpath("//li[contains(@id,'menu-item')]//a/@href").extract();# 去除首页和后面的分栏for idx in range(1,7):# 建立文件夹# 先确定要存储的位置path = settings.SAVE_PATH+lm_name[idx];# 判断是否有该位置的文件夹,没有则重新建立if not os.path.exists(path):os.mkdir(path);# 打印栏目的名称和urlprint(">>> [parse] -> 栏目名称 : %s url : %s" % (lm_name[idx],lm_url[idx]));# 产生一个itemitem = AcgItem();item["dir_name"] = str(lm_name[idx]);item["lm_url"] = str(lm_url[idx]);item["page_num"] = 1;# 这里预留图像的url和img_name的值# 生成generatoryield scrapy.Request(lm_url[idx], meta={"item":item}, callback=self.parse1);
这里程序也比较简单,也带有我个人认为比较清楚的注释,我的建议是最好亲手撸一遍,过程中不懂的就baidu, bing, google,一般都能找到满意的回答。
这里也着重说一下具有好感的Item,开始在接触这个框架的时候就在想如何在请求与请求之间传递参数,例如本例中的下载路径文件夹,这显然是要在不停的解析下传递下去的,看到Item我就放心了,Scrapy确实为我们想好了策略,而且这个Item还是字典的形式,各种取值赋值都很简单,这里主要还是提醒说在传递的时候,主要通过scrapy.Request中的meta变量进行传递,而且程序中也可以清晰的看到,传递的参数可以是一个,也可以是很多个的!只要把键值区分开就行。最后,一旦觉得实体可以给Pipeline了,直接yield一下该Item就行,太方便了。
Item代码
# -*- coding: utf-8 -*-# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.htmlimport scrapyclass AcgItem(scrapy.Item):# define the fields for your item here like:# name = scrapy.Field()# 文件夹的名称dir_name = scrapy.Field(serializer=str);lm_url = scrapy.Field(serializer=str);page_num = scrapy.Field(serializer=int);img_url = scrapy.Field(serializer=str);img_name = scrapy.Field(serializer=str);
这里Scrapy的Field有一个很好的属性就是serializer,后面的配置可以限制这个变量的类型。
Pipeline代码
# -*- coding: utf-8 -*-# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
from acg.items import AcgItem;
from acg import settings;
from http.client import IncompleteRead;import urllib.error;
import urllib.request;
import urllib.parse;
import numpy as np;
import cv2;
import os;class AcgPipeline(object):''' 初始化函数 '''def __init__(self):self.img_seen = set();''' 执行函数 '''def process_item(self, item, spider):# 判断是否见过这个image_nameif item["img_name"] in self.img_seen:# 如果是的话打印出信息print(">>> [pipeline] -> old url is used!");# 并返回return item;else:# 把图片给resize一下,防止过大maxSize = 512;# 获取urlurl = item["img_url"];# 模仿浏览器进行访问req = urllib.request.Request(url=url);req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1");# req.add_header("GET",url);# req.add_header("Host", "img.gov.com.de");req.add_header("Referer", url);# 数据的获取try:# 获取该数据res = urllib.request.urlopen(req, timeout=100).read();# 转化为numpy类型的数据image = np.asarray(bytearray(res), dtype="uint8");# 转化为opencv的数据image = cv2.imdecode(image, cv2.IMREAD_COLOR);# reszieimgResize = cv2.resize(image, dsize=(maxSize, maxSize), interpolation=cv2.INTER_CUBIC);# 存储# 先确定要存储的位置path = settings.SAVE_PATH+item["dir_name"];# 判断是否有该位置的文件夹,没有则重新建立if not os.path.exists(settings.SAVE_PATH):os.makedirs(path);# 生成图片保存的路径img_path = path + "/" + item["img_name"] + ".jpg";# 保存图片cv2.imwrite(img_path, imgResize);# 打印保存信息print(">>> [pipeline] -> image saves in %s" % img_path);except urllib.error.HTTPError as e:print(">>> [pipeline] -> HTTPError occur, it's code is %s" % str(e.code));except (IncompleteRead) as e:print(">>> [pipeline] -> IncompleteRead occur, it's code is %s" % str(e.code));except urllib.error.URLError as e:print(">>> [pipeline] -> URLError occur, it's code is %s" % str(e.code));# 别忘了返回itemreturn item;
这部分代码也没有特别多的地方需要讲,硬说的话主要是在urllib的urlopen时尽量加上timeout的设置。
PS:如果要用Pipeline进行资源下载,一定要在setting里面把关于Pipeline的注解删掉并加上自己的Pipeline名称,例如本程序中是下面代码所示的样子,然后最后的数字是0~1000之间就行,表示优先级,具体的可以看注释的网址:D
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {'acg.pipelines.AcgPipeline': 300,
}
最终结果
可以看到最后是完成了我们的愿想:D
总结
1. Python真心强大,这几天还在继续看一些基础的教程,发现很多很好用的语句和内置函数(太TM强大了,让在C和C++摸爬滚打的我十分佩服),还是很值得学习一下
2. Scrapy真心强大,分布式的抓取,速度很快,但是看网上的建议说速度太快也不好,应该加个延时,但是我在跑程序的时候并没有遇到锁IP的情况,因此就没有加延时
3. 整个程序里面有很多不太规范的地方,比如每句后面都加了分号,而且字符串大多数都用的是双影号,主要是为了和C++编程习惯一致,毕竟以后还要靠C++吃饭:x
4. 最后是整个程序的代码,如果想作为参考的可以到GitHub上下载,链接:https://github.com/wuRDmemory/acg_spider
以上的所有都是个人的理解,如果有错误,还请各位大神能够指点纠正,在此不胜感谢!
这篇关于Python初识——Scrapy抓取二次元小姐姐图片的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!