一、什么是 scrap?
爬虫中封装好的一个明星框架。
功能:高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式
二、scrap框架的基本使用
- 环境的安装
- mac or Linux: pip install scrap
- windows
- pip install wheel
- 下载twisted:下載地址为http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
- 安装twisted:pip install Twisted-17.1.0-cp36-cp36m-win amd64 whl
- pip install pywin32
- pip install scrapy
测试:在终端里录入 scrap指令,没有报错即表示安装成功!
- 创建一个工程:
scrapy startproject xxxpro(文件名)
cd xxxpro
在spiders子目录中创建一个爬虫文件
scrapy genspider spiderName wwww.xxx.com
执行工程
scrapy crawl spiderName
三、scrapy数据解析
实例:以爬取糗事百科的段子为例
1、先创建项目
2、创建爬虫文件
爬虫文件的代码如下:
import scrapy
class QiushiSpider(scrapy.Spider):
name = 'qiushi'
# allowed_domains = ['https://www.qiushibaike.com/text/']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
#解析:作者的名称+段子内容
div_list = response.xpath('//*[@class="col1 old-style-col1"]/div')
for div in div_list:
#xpath返回的是列表,但是列表元素一定是select类型的对象
#extract可以将select对象中的data参数存储的字符串提取出来
author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
#列表调用了extract之后,则表示将列表中的每一个select对象中的data对应的字符串提取了出来
content = div.xpath('./a[1]/div/span//text()').extract()
content = ''.join(content)
print(author,content)
break
运行代码前修改settings.py 文件
运行代码
四、scrapy 持久化存储
- 基于终端指令:
- 要点:只可以将parse方法返回的值存储到本地的文本之中
- 注意:持久化存储对应的文本文件的类型只可以为:(‘json’, ‘jsonlines’, ‘jl’, ‘csv’, ‘xml’, ‘marshal’, ‘pickle’)
- 指令:scrapy crawl xxx(爬虫文件名) -o filename(存储文件的名称)
- 好久:简介高效便捷
- 缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)
爬虫文件代码:
import scrapy
class QiushiSpider(scrapy.Spider):
name = 'qiushi'
# allowed_domains = ['https://www.qiushibaike.com/text/']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
#解析:作者的名称+段子内容
div_list = response.xpath('//*[@class="col1 old-style-col1"]/div')
all_data = []
for div in div_list:
#xpath返回的是列表,但是列表元素一定是select类型的对象
#extract可以将select对象中的data参数存储的字符串提取出来
author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
#列表调用了extract之后,则表示将列表中的每一个select对象中的data对应的字符串提取了出来
content = div.xpath('./a[1]/div/span//text()').extract()
content = ''.join(content)
dic = {
'author':author,
'content':content
}
all_data.append(dic)
return all_data
- 基于管道:
- 编码流程:
-
数据解析
-
在item类中定义相关的属性
-
将解析的数据封装存储到item类型的对象
-
将item类型的对象提交给管道进行持久化存储的操作
-
在管道类的process_item中要将其接受到item对象中存储的数据进行持久化存储操作
-
在配置文件中开启管道
-
- 好处
-通用性强
- 编码流程:
五、全站数爬取
以爬取校花网所有的图片
爬虫的代码如下(settings里面的设置和上面一样):
import scrapy
class XhSpider(scrapy.Spider):
name = 'xh'
# allowed_domains = ['www.xx.com']
start_urls = ['http://www.521609.com/meinvxiaohua/list122.html']
#生成一个通用的url模板
url = 'http://www.521609.com/meinvxiaohua/list12%d.html'
page_num = 2
def parse(self, response):
li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')
for li in li_list:
image_name = li.xpath('./a[2]/text() | ./a[2]/b/text()').extract_first()
print(image_name)
if self.page_num <11:
self.page_num += 1
new_url = format(self.url%self.page_num)
#手动发送请求:callback回调函数作用于数据解析
yield scrapy.Request(url=new_url,callback=self.parse)
回调函数:
在Python中,已经没有指针这个说法了,一般都是说函数名。简单来说就是定义一个函数,然后将这个函数的函数名传递给另一个函数做参数,以这个参数命名的函数就是回调函数。
def my_callbcak(args):
print(*args)
def caller(args, func):
func(args)
caller((1,2), my_callbcak)
结果:
# 1 2
其中:my_callback是回调函数,因为它作为参数传递给了caller
六、五大核心组件
- 请求传参—以爬取boss职称和简介为例
爬虫文件下的代码:
import scrapy
from bosspro.items import BossproItem
class BossSpider(scrapy.Spider):
name = 'boss'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.zhipin.com/job_detail/?query=python&city=101280600&industry=&position=']
url = 'https://www.zhipin.com/c101280600/?query=python&page=%d'
page_nem = 2
#回调函数
def parse_deital(self,response):
item = response.meta['item']
job_desc = response.xpath('//*[@id="main"]/div/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract_first()
job_desc = ''.join(job_desc)
item['job_desc'] = job_desc
def parse(self, response):
li_list = response.xpath('//*[@id="main"]/div/div[3]/ul/li')
for li in li_list:
item = BossproItem()
job_name = li.xpath('.//div[@class="info-primary"]/h3/a/div[1]/text()').extract_first()
item['job_name'] = job_name
detail_url = li.xpath('.//div[@class="info-primary"]/h3/a/@href').extract_first()
# 对详情页发起请求,获取页面源码数据
# 手动请求的发送
#请求参数:meta={},可以将meta字典传递给请求对应的回调函数
yield scrapy.Request(detail_url,callback=self.parse_deital,meta={'item':item})
#进行分页处理
if self.page_nem <= 3:
new_url = format(self.url%self.page_nem)
self.page_nem += 1
yield scrapy.Request(new_url,callback=self.parse)
- scrapy爬取图片(懒加载)
- 使用ImagesPipeline:只需要将img的src的属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制
- 需求:爬取站在素材中的高清图片
- 使用流程
- 数据解析(图片的地址)
- 将存储图片地址的item提交到指定的管道类
- 在管道文件中自定制一个基于ImagePipeline的一个管道类,重写以下方法
- get _meia_request
- file_path
- item_completed
- 在配置文件中
- 指定图片存储的目录:IMAGES_STORE = ‘./imgs_cc’ (自定义)
- 指定开启的管道:自定制的管道类
爬虫文件代码:
import scrapy
from imgpro.items import ImgproItem
class ImgSpider(scrapy.Spider):
name = 'img'
# allowed_domains = ['www.xx.com']
start_urls = ['https://sc.chinaz.com/tupian/']
def parse(self, response):
img_list = response.xpath('//*[@id="container"]/div')
for img in img_list:
#使用伪属性,因为图片是懒加载的,图片加载出来后才是src,加载前是src2(只针对此需求而言)
src ='https:' + img.xpath('./div/a/img/@src2').extract_first()
item = ImgproItem()
item['src'] = src
yield item
items下的代码:
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class ImgproItem(scrapy.Item):
# define the fields for your item here like:
src = scrapy.Field()
pass
pipeline下的代码:
from scrapy.pipelines.images import ImagesPipeline
import scrapy
class ImgsPipeline(ImagesPipeline):
#根据图片地址进行图片数据的请求
def get_media_requests(self,item,info):
yield scrapy.Request(item['src'])
#指定图片存储的路径
def file_path(self,request,response=None,info=None):
img_name = request.url.split('/')[-1]
return img_name
def item_completed(self, results, item, info):
return item #返回给下一个即将被执行的管道类
settings下的代码:
BOT_NAME = 'imgpro'
SPIDER_MODULES = ['imgpro.spiders']
NEWSPIDER_MODULE = 'imgpro.spiders'
LOG_LEVEL = 'ERROR'
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' \
' Chrome/89.0.4389.90 Safari/537.36'
ROBOTSTXT_OBEY = False
ITEM_PIPELINES = {
'imgpro.pipelines.ImgsPipeline': 300,
}
#指定图片存储的路径
IMAGES_STORE = './img_cc'
六、中间件
- 拦截响应
需求:爬取网易新闻中的新闻数据(标题和内容)
爬虫代码:
import scrapy
from selenium import webdriver
from middle.items import MiddleItem
class MidSpider(scrapy.Spider):
name = 'mid'
# allowed_domains = ['www.xx.com']
start_urls = ['https://news.163.com/']
model_urls = []
#实例化一个浏览器对象
def __init__(self):
self.bro = webdriver.Chrome()
def parse(self, response):
li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
alist = [3,4] #自己要爬取的模块对应的坐标值
for index in alist:
model_url= li_list[index].xpath('./a/@href').extract_first() #获取每个版本的url地址
self.model_urls.append(model_url)
#依次对每一个板块对应页面进行请求
for url in self.model_urls:
yield scrapy.Request(url,callback=self.parse_model)
#解析每一个板块对应的新闻的标题和新闻详情页的url
def parse_model(self,response):
div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div[1]/div/ul/li/div/div')
for div in div_list:
title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
new_detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
item = MiddleItem()
item['title'] = title
#对新闻详情页的url发起请求
yield scrapy.Request(url=new_detail_url,callback=self.parse_detail,meta={'item':item})
def parse_detail(self,response):
content = response.xpath('//*[@id="content"]//text()').extract()
content = ''.join(content)
item = response.meta['item']
item['content'] = content
yield item
def closed(self,spider):
self.bro.quit()
items代码:
import scrapy
class MiddleItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
content = scrapy.Field()
middlewares代码:
from scrapy import signals
from itemadapter import is_item, ItemAdapter
import random
from scrapy.http import HtmlResponse
import time
class MiddleDownloaderMiddleware:
#拦截请求
def process_request(self, request, spider):
#UA伪装
# request.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' \
# ' Chrome/89.0.4389.90 Safari/537.36'
#拦截响应
def process_response(self, request, response, spider): #spider爬虫对象
bro = spider.bro # 获取在爬虫类中定义的浏览器对象
#挑选出指定的响应对象进行篡改
#通过指定url指定request
#通过request指定response
if request.url in spider.model_urls:
bro.get(request.url) #对指定模块进行请求
time.sleep(2)
page_text = bro.page_source #包含了动态加载的新闻数据
#response 指定请求的响应对象
#针对定位到的response进行篡改
#实例化一个新的响应对象,替换原来旧的响应对象
new_reponse = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
return new_reponse
else:
#其他请求的响应对象
return response
#拦截发生异常的请求
def process_exception(self, request, exception, spider):
# 为了验证代理的操作是否生效
#request.meta['proxy'] = '123.156.182.33:8888'
pipeline代码:
class MiddlePipeline:
def process_item(self, item, spider):
print()
return item
settings代码:
BOT_NAME = 'middle'
SPIDER_MODULES = ['middle.spiders']
NEWSPIDER_MODULE = 'middle.spiders'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
DOWNLOADER_MIDDLEWARES = {
'middle.middlewares.MiddleDownloaderMiddleware': 543,
}
ITEM_PIPELINES = {
'middle.pipelines.MiddlePipeline': 300,
}
七、CrawlSpider类
是属于Spider的一个子类
-
CrawlSpider的使用
- 创建一个工程
- cd xx(工程目录)
- 创建爬虫文件:scrapy genspider -t crawl xxx(爬虫文件名) www.xxx.com
- 链接提取器:根据指定规则(allow=“正则”)进行指定链接的提取
- 规则解析器:将链接提取器提取到的链接进行指定规则(callback) 的解析操作
- follow=True:可以将链接提取器 继续作用到 链接提取器提取到的链接 所对应的页面中(例如:网站页面默认只显示5个页码,当为False的时候就只能提取到5个页码的链接信息,当为True的时候,就可以提取到所有页码的链接信息)
-
实例:爬取阳光热线的标题,内容和编号
爬虫文件代码:
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from yg.items import YgItem,YgDetail
class YgcSpider(CrawlSpider):
name = 'ygc'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://wz.sun0769.com/political/index/supervise?page=']
link_detail = LinkExtractor(allow=r'id=\d+')
# (LinkExtractor)链接提取器:根据指定规则(allow="正则")进行指定链接的提取
# (rules)规则解析器:将链接提取器提取到的链接进行指定规则
# follow=True:可以将链接提取器 继续作用到 链接提取器提取到的链接 所对应的页面中
rules = (
Rule(LinkExtractor(allow=r'page=\d+'), callback='parse_item', follow=True),
Rule(link_detail,callback='parse_detail')
)
#解析新闻编号和新闻标题
#如下两个解析方法中是不可以实现请求传参的
#如法将两个解析的数据存储到同一个item中,可以以此存储到两个item
def parse_item(self, response):
#注意:xpath表达式中不可以出现tbody标签
li_list = response.xpath('/html/body/div[2]/div[3]/ul/li')
for li in li_list:
new_num = li.xpath('./span[1]/text()').extract_first()
new_title = li.xpath('./span[3]/a/text()').extract_first()
item = YgItem()
item['new_num'] = new_num
item['new_title'] = new_title
yield item
#解析新闻内容和新闻编号
def parse_detail(self,response):
new_id = response.xpath('/html/body/div[3]/div[2]/div[2]/div[1]/span[4]/text()').extract_first()
content= response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
item = YgDetail()
item['new_id'] = new_id
item['content'] = content
yield item
items代码:
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class YgItem(scrapy.Item):
# define the fields for your item here like:
new_num = scrapy.Field()
new_title = scrapy.Field()
class YgDetail(scrapy.Item):
new_id = scrapy.Field()
content = scrapy.Field()
pipeline代码:
from itemadapter import ItemAdapter
class YgPipeline:
def process_item(self, item, spider):
if item.__class__.__name__ == 'YgItem':
print(item['new_num'],item['new_title'])
else:
print(item['new_id'],item['content'])
return item
settings代码:
BOT_NAME = 'yg'
SPIDER_MODULES = ['yg.spiders']
NEWSPIDER_MODULE = 'yg.spiders'
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' \
' Chrome/89.0.4389.90 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
ITEM_PIPELINES = {
'yg.pipelines.YgPipeline': 300,
}