Scrapy 爬虫的简单使用
简单的爬虫流程
一般的,程序向网站请求网页
程序 =========> 网站
而后,网站返回网页与程序
程序 <========= 网站
接着程序负责解析得到的 HTML 数据即可
Scrapy 如何扩展这一流程
程序请求网页时, scrapy
添加了中间件在双方之间
程序 =====> 中间件 ======> 网站
网站返回数据时,也需要通过中间件
程序 <===== 中间件 <====== 网站
程序解析完数据后,不会马上处理数据,而是把一个网页的数据打包成一个 Item
数据,传递给 pipeline
处理
pipeline <==== item <==== 程序
Scrapy 简单示例
这里以爬取美图录的图片为例(注意,我不打算用默认的 start_urls
数据)
首先我们到一个模特的主页上,比如
接下来我们要从这个 指定模特页面 上的每一个专辑上爬取图片
准备项目
-
安装
scrapy
sudo pip3 install scrapy
-
创建新项目
scrapy startproject meitulu
基础设定
在 meitulu/spiders/
下我们新建一个爬虫文件 evelyn.py
class Spider(scrapy.Spider):
name = 'evelyn'
其中的 name
属性是为了调用的时候能找到爬虫位置,比如调用这个爬虫的时候,我们指定 name
scrapy crawl evelyn
添加命令行参数
def __init__(self, albumUrl=None, *args, **kwargs):
if albumUrl == None:
raise Exception('usage: -a albumUrl=...')
super(Spider, self).__init__(*args, **kwargs)
self.start_url = albumUrl
def start_requests(self):
yield scrapy.Request(url=self.start_url, callback=self.parse)
这里在类的 __init__
方法上我们做了修改,第二个参数是命令行参数名称,默认为 None
,第三,第四个参数不需要管
我们这样调用
scrapy crawl evelyn -a albumUrl='http://meitulu.cn/t/Evelynaili/'
注意这里我们没有定义 start_urls
而是 start_url
,这里我覆盖了这种默认行为,重新定义了 start_requests
并将获取到的响应交给 self.parse
回调来解析
解析函数 parseAlbum
这里的函数解析一张专辑中的每一张图片,然后解析出 item
,传递给 pipeline
,如果有下一页,继续请求
def parseAlbum(self, response, firstUrl, albumName, count):
url = response.css('div.content center a img.content_img::attr(src)').extract_first()
name = str(count) + '.jpg'
yield Image(url=url, name=name, albumName=albumName, fromUrl=response.url)
nextPage = response.urljoin(response.css('div.content center a::attr(href)').extract_first())
if nextPage != firstUrl:
yield scrapy.Request(
url=nextPage,
callback=self.parseAlbum,
cb_kwargs=dict(firstUrl=nextPage, albumName=albumName, count=count+1))
在函数中,
firstUrl
表示专辑的第一页网址,也是专辑的入口地址,设置他是因为最后一页的下一页是firstUrl
, 需要用来判断当前页是否为最后一页albumName
表示专辑名称,设置他是因为我看了下html代码,每一张图片的alt
属性都是专辑名,为其命名也麻烦count
所以我就设置了这个参数,需要请求下一页的时候,这个参数就加1
解析函数 parse
这个函数获取模特主页中每一张专辑的地址,然后传递给 self.parseAlbum
回调,并通过 cb_kwargs
添加额外的函数参数
def parse(self, response):
urls = response.css('div.main div.boxs ul.img li>a::attr(href)').extract()
albumUrls = list(map(lambda url: response.urljoin(url), urls))
names = response.css('div.main div.boxs ul.img li p.p_title> a::text').extract()
for (albumUrl, name) in zip(albumUrls, names):
yield scrapy.Request(
url=albumUrl,
callback=self.parseAlbum,
cb_kwargs=dict(firstUrl=albumUrl, albumName=name, count=1))
Pipeline 处理结果
由于直接请求图片会被网站检测,并返回 403 代码,这里我们伪造下请求头
default_headers = {
'Accept': 'image/avif,image/webp,*/*',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Host': 'image.meitulu.cn',
'Referer': 'http://meitulu.cn/',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0'
}
接下来我们写一个类来继承 FilesPipeline
,处理结果
from scrapy.pipelines.images import FilesPipeline
class MeituluPipeline(FilesPipeline):
default_headers = {
'Accept': 'image/avif,image/webp,*/*',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Host': 'image.meitulu.cn',
'Referer': 'http://meitulu.cn/',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0'
}
def file_path(self, request, response=None, info=None, *, item=None):
dirname = item['albumName']
basename = item['name']
return os.path.join(dirname, basename)
def get_media_requests(self, item, info):
yield scrapy.Request(item['url'], headers=self.default_headers)
def item_completed(self, results, item, info):
return item
其中有三个函数可以挑选着来重写
file_path
指定item
存贮的文件名get_media_requests
自定义如何请求图片的函数item_completed
下载完图片后如何处理
另外这个 file_path
的重写我忘了从哪个教程里粘贴过来的了,先用着吧
全局设置
开启 pipeline
ITEM_PIPELINES = {
'meitulu.pipelines.MeituluPipeline': 300,
}
设置 文件存贮 默认位置
FILES_STORE = '/home/steiner/workspace/meitulu/capture/'