什么是框架?
- 就是一个集成了很多功能并且具有很强通用性的一个项目模板
如何学习框架?
- 专门学习框架封装的各种功能的详细用法
什么是scrapy?
- 爬虫中封装好的一个明星框架。功能:异步的数据下载,高性能的数据解析,高性能的持久化存储,分布式
scrapy框架的基本使用:
-
环境的安装:
-
- mac or linux:pip install scrapy - windows:以下命令可以直接在interpreter里面下载对应的包 - 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 测试:在终端里录入scrapy指令,没有报错即表示安装成功!
-
-
创建一个工程:scrapy startproject xxxPro
-
进入该工程:cd xxxPro
-
在spiders子目录中创建一个爬虫文件:scrapy genspider spiderName www.xxx.com
-
执行工程:scrapy crawl spiderName
scrapy的使用
爬虫文件:first.py
import scrapy
class FirstSpider(scrapy.Spider):
#爬虫文件的名称:就是爬虫源文件的一个唯一标识
name = 'first'
#允许的域名:用来限定start_urls列表中哪些url可以进行请求发送
# allowed_domains = ['www.baidu.com']
#起始的url列表:该列表中存放的url会被scrapy自动进行请求的发送
start_urls = ['https://www.baidu.com/','https://www.sogou.com']
#用作于数据解析:response参数表示的就是请求成功后对应的响应对象
def parse(self, response):
print(response)
配置文件:settings.py
#常规的设置
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR' #显示指定类型的日志信息
scrapy持久化存储:
-
基于终端指令:
-
- 要求:只可以将parse方法的返回值存储到本地的文本文件中 - 注意:持久化存储对应的文本文件的类型只可以为:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle - 指令:scrapy crawl spiderName -o filePath - 好处:简介高效便捷 - 缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)
-
def parse(self, response): #解析:作者的名称+段子内容 div_list = response.xpath('//div[@id="content-left"]/div') all_data = [] #存储所有解析到的数据 for div in div_list: #xpath返回的是列表,但是列表元素是Selector类型的对象 #extract可以将Selector对象中data参数存储的字符串提取出来 # author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract() author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() #列表调用了extract之后,则表示将列表中每一个Selector对象中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对象中存储的数据进行持久化存储操作 - 在配置文件中开启管道 - 好处: - 通用性强
-
#数据解析+将解析的数据封装存储到item类型的对象+将item类型的对象提交给管道进行持久化存储的操作 #first.py from qiubaiPro.items import QiubaiproItem def parse(self, response): #解析:作者的名称+段子内容 div_list = response.xpath('//div[@id="content-left"]/div') all_data = [] #存储所有解析到的数据 for div in div_list: author = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span/h2/text()').extract_first() content = div.xpath('./a[1]/div/span//text()').extract() content = ''.join(content) item = QiubaiproItem() item['author'] = author item['content'] = content yield item #将item提交给了管道
-
#在item类中定义相关的属性 #items.py import scrapy class QiubaiproItem(scrapy.Item): author = scrapy.Field() content = scrapy.Field()
-
#在管道类的process_item中要将其接受到的item对象中存储的数据进行持久化存储操作 #pipelines.py class QiubaiproPipeline(object): fp = None #重写父类的一个方法:该方法只在开始爬虫的时候被调用一次 def open_spider(self,spider): print('开始爬虫......') self.fp = open('./qiubai1.txt','w',encoding='utf-8') #专门用来处理item类型对象,该方法可以接收爬虫文件提交过来的item对象 #该方法每接收到一个item就会被调用一次 def process_item(self, item, spider): author = item['author'] content= item['content'] self.fp.write(author+':'+content+'\n') return item #就会传递给下一个即将被执行的管道类 #重写父类的一个方法:该方法只在结束爬虫的时候被调用一次 def close_spider(self,spider): print('结束爬虫!') self.fp.close()
-
持久化存储的拓展:将爬取到的数据一份存储到本地一份存储到数据库,如何实现?
-
管道文件中一个管道类对应的是将数据存储到一种平台
-
爬虫文件提交的item只会给管道文件中第一个被执行的管道类接收
-
process_item中的return item表示将item传递给下一个即将被执行的管道类
-
#pipelines.py import pymysql class QiubaiproPipeline(object): fp = None def open_spider(self,spider): print('开始爬虫......') self.fp = open('./qiubai1.txt','w',encoding='utf-8') def process_item(self, item, spider): author = item['author'] content= item['content'] self.fp.write(author+':'+content+'\n') return item #就会传递给下一个即将被执行的管道类 def close_spider(self,spider): print('结束爬虫!') self.fp.close() class mysqlPileLine(object): conn = None cursor = None def open_spider(self,spider): self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='qiubai',charset='utf8') def process_item(self,item,spider): self.cursor = self.conn.cursor() try: self.cursor.execute('insert into qiubai values("%s","%s")'%(item["author"],item["content"])) self.conn.commit() except Exception as e: print(e) self.conn.rollback() return item def close_spider(self,spider): self.cursor.close() self.conn.close()
-
#settings.py ITEM_PIPELINES = { 'qiubaiPro.pipelines.QiubaiproPipeline': 300, 'qiubaiPro.pipelines.mysqlPileLine': 301, #300表示的是优先级,数值越小优先级越高 }
基于Spider的全站数据爬取
基于Spider的全站数据爬取:就是将网站中某板块下的全部页码对应的页面数据进行爬取(递归调用)
需求:爬取校花网中的照片的名称
实现方式:
- 将所有页面的url添加到start_urls列表(不推荐)
- 自行手动进行请求发送(推荐):yield scrapy.Request(url,callback): callback专门用做于数据解析
#xiaohua.py
import scrapy
class XiaohuaSpider(scrapy.Spider):
name = 'xiaohua'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://www.521609.com/meinvxiaohua/']
#生成一个通用的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:
img_name = li.xpath('./a[2]/b/text() | ./a[2]/text()').extract_first()
print(img_name)
if self.page_num <= 11:
new_url = format(self.url%self.page_num)
self.page_num += 1
#手动请求发送:callback回调函数是专门用作于数据解析
yield scrapy.Request(url=new_url,callback=self.parse)
scrapy五大核心组件:
引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心)
调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
下载器(Downloader)
用于下载网页内容, 并将网页内容返回给爬虫Spiders(Scrapy下载器是建立在twisted这个高效的异步模型上的)
爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据
请求传参
- 使用场景:如果爬取解析的数据不在同一张页面中(深度爬取)
- 需求:爬取boss的岗位名称,岗位描述
#boss.py
import scrapy
class BossSpider(scrapy.Spider):
name = 'boss'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.zhipin.com/job_detail/?query=python&city=101010100&industry=&position=']
url = 'https://www.zhipin.com/c101010100/?query=python&page=%d'
page_num = 2
#回调函数接受item
def parse_detail(self,response):
item = response.meta['item']
job_desc = response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract()
job_desc = ''.join(job_desc)
print(job_desc)
item['job_desc'] = job_desc
yield item #传递给管道
#解析首页中的岗位名称
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
print(job_name)
detail_url = 'https://www.zhipin.com'+li.xpath('.//div[@class="info-primary"]/h3/a/@href').extract_first()
#对详情页发请求获取详情页的页面源码数据
#手动请求的发送
#请求传参:meta={},可以将meta字典传递给请求对应的回调函数
yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
#分页操作
if self.page_num <= 3:
new_url = format(self.url%self.page_num)
self.page_num += 1
yield scrapy.Request(new_url,callback=self.parse)
#items.py
import scrapy
class BossproItem(scrapy.Item):
job_name = scrapy.Field()
job_desc = scrapy.Field()
图片数据爬取之ImagesPipeline
基于scrapy爬取字符串类型数据和爬取图片类型数据的区别?
- 字符串:只需要基于xpath进行解析且提交管道进行持久化存储
- 图片:xpath解析出图片src的属性值,单独的对图片地址发起请求获取图片二进制类型的数据
ImagesPipeline:
- 只需要将img的src的属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制类型的数据,且还会帮我们进行持久化存储
需求:爬取站长素材中的高清图片
使用流程:
- 数据解析(图片的地址)
- 将存储图片地址的item提交到制定的管道类
- 在管道文件中自定制一个基于ImagesPipeLine的一个管道类
- get_media_request
- file_path
- item_completed
- 在配置文件中:
- 指定图片存储的目录:IMAGES_STORE = './imgs_bobo'
- 指定开启的管道:自定制的管道类
#items.py
import scrapy
class ImgsproItem(scrapy.Item):
src = scrapy.Field()
#img.py
import scrapy
from imgsPro.items import ImgsproItem
class ImgSpider(scrapy.Spider):
name = 'img'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://sc.chinaz.com/tupian/']
def parse(self, response):
div_list = response.xpath('//div[@id="container"]/div')
for div in div_list:
#注意:使用伪属性
src = div.xpath('./div/a/img/@src2').extract_first()
item = ImgsproItem()
item['src'] = src
yield item
#pipelines.py
from scrapy.pipelines.images import ImagesPipeline
import scrapy
class imgsPileLine(ImagesPipeline):
#可以根据图片地址进行图片数据的请求
def get_media_requests(self, item, info):
yield scrapy.Request(item['src'])
#指定图片存储的路径
def file_path(self, request, response=None, info=None):
imgName = request.url.split('/')[-1]
return imgName
def item_completed(self, results, item, info):
return item #返回给下一个即将被执行的管道类
#settings.py
#指定图片存储的目录
IMAGES_STORE = './imgs_bobo'
ITEM_PIPELINES = {
'imgsPro.pipelines.imgsPileLine': 300,
}
中间件
爬虫中间件
- 位置:引擎和spider之间
下载中间件
- 位置:引擎和下载器之间
- 作用:批量拦截到整个工程中所有的请求和响应
- 拦截请求:
- UA伪装: process_request
- 代理IP: process_exception: return request
- 拦截响应:
- 篡改响应数据,响应对象
- 需求:爬取网易新闻中的新闻数据(标题和内容)
- 1.通过网易新闻的首页解析出五大板块对应的详情页的url(没有动态加载)
- 2.每一个板块对应的新闻标题都是动态加载出来的(动态加载)
- 3.通过解析出每一条新闻详情页的url获取详情页的页面源码,解析出新闻内容
拦截请求:
#middle.py
import scrapy
class MiddleSpider(scrapy.Spider):
#爬取百度
name = 'middle'
# allowed_domains = ['www.xxxx.com']
start_urls = ['http://www.baidu.com/s?wd=ip']
def parse(self, response):
page_text = response.text
with open('./ip.html','w',encoding='utf-8') as fp:
fp.write(page_text)
#middlewares.py
import random
class MiddleproDownloaderMiddleware(object):
user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
"(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
]
PROXY_http = [
'153.180.102.104:80',
'195.208.131.189:56055',
]
PROXY_https = [
'120.83.49.90:9000',
'95.189.112.214:35508',
]
#拦截请求
def process_request(self, request, spider):
#UA伪装
request.headers['User-Agent'] = random.choice(self.user_agent_list)
#为了验证代理的操作是否生效
request.meta['proxy'] = 'http://183.146.213.198:80'
return None
#拦截所有的响应
def process_response(self, request, response, spider):
return response
#拦截发生异常的请求
def process_exception(self, request, exception, spider):
if request.url.split(':')[0] == 'http':
#代理
request.meta['proxy'] = 'http://'+random.choice(self.PROXY_http)
else:
request.meta['proxy'] = 'https://' + random.choice(self.PROXY_https)
return request #将修正之后的请求对象进行重新的请求发送
#settings.py
DOWNLOADER_MIDDLEWARES = {
'middlePro.middlewares.MiddleproDownloaderMiddleware': 543,
}
拦截响应:
#items.py
import scrapy
class WangyiproItem(scrapy.Item):
title = scrapy.Field()
content = scrapy.Field()
#wangyi.py
import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem
class WangyiSpider(scrapy.Spider):
name = 'wangyi'
# allowed_domains = ['www.cccom']
start_urls = ['https://news.163.com/']
models_urls = [] #存储五个板块对应详情页的url
#解析五大板块对应详情页的url
#实例化一个浏览器对象
def __init__(self):
self.bro = webdriver.Chrome(executable_path='/Users/bobo/Desktop/小猿圈爬虫课程/chromedriver')
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,6,7,8]
for index in alist:
model_url = li_list[index].xpath('./a/@href').extract_first()
self.models_urls.append(model_url)
#依次对每一个板块对应的页面进行请求
for url in self.models_urls:#对每一个板块的url进行请求发送
yield scrapy.Request(url,callback=self.parse_model)
#每一个板块对应的新闻标题相关的内容都是动态加载
def parse_model(self,response): #解析每一个板块页面中对应新闻的标题和新闻详情页的url
# response.xpath()
div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/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 = WangyiproItem()
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="endText"]//text()').extract()
content = ''.join(content)
item = response.meta['item']
item['content'] = content
yield item
def closed(self,spider):
self.bro.quit()
#middlewares.py
from scrapy.http import HtmlResponse
from time import sleep
class WangyiproDownloaderMiddleware(object):
def process_request(self, request, spider):
return None
#该方法拦截五大板块对应的响应对象,进行篡改
def process_response(self, request, response, spider):#spider爬虫对象
bro = spider.bro#获取了在爬虫类中定义的浏览器对象
#挑选出指定的响应对象进行篡改
#通过url指定request
#通过request指定response
if request.url in spider.models_urls:
bro.get(request.url) #五个板块对应的url进行请求
sleep(3)
page_text = bro.page_source #包含了动态加载的新闻数据
#response #五大板块对应的响应对象
#针对定位到的这些response进行篡改
#实例化一个新的响应对象(符合需求:包含动态加载出的新闻数据),替代原来旧的响应对象
#如何获取动态加载出的新闻数据?
#基于selenium便捷的获取动态加载数据
new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
return new_response
else:
#response #其他请求对应的响应对象
return response
def process_exception(self, request, exception, spider):
pass
#settings.py
DOWNLOADER_MIDDLEWARES = {
'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
}
ITEM_PIPELINES = {
'wangyiPro.pipelines.WangyiproPipeline': 300,
}
基于CrawlSpider的全站数据爬取
CrawlSpider类,Spider的一个子类
全站数据爬取的方式:
- 基于Spider:手动请求
- 基于CrawlSpider
CrawlSpider的使用:
- 创建一个工程
- cd XXX
- 创建爬虫文件(CrawlSpider):scrapy genspider -t crawl xxx www.xxxx.com
链接提取器作用:根据指定的规则(allow)进行指定链接的提取
规则解析器作用:将链接提取器提取到的链接进行指定规则(callback)的解析
需求:爬取sun网站中的编号,新闻标题,新闻内容,标号
分析:爬取的数据没有在同一张页面中
-
1.可以使用链接提取器提取所有的页码链接
-
2.让链接提取器提取所有的新闻详情页的链接
#items.py
import scrapy
class SunproItem(scrapy.Item):
title = scrapy.Field()
new_num = scrapy.Field()
class DetailItem(scrapy.Item):
new_id = scrapy.Field()
content = scrapy.Field()
#sun.py
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.items import SunproItem,DetailItem
#需求:爬取sun网站中的编号,新闻标题,新闻内容,标号
class SunSpider(CrawlSpider):
name = 'sun'
start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']
#链接提取器:根据指定规则(allow="正则")进行指定链接的提取
link = LinkExtractor(allow=r'type=4&page=\d+')
link_detail = LinkExtractor(allow=r'question/\d+/\d+\.shtml')
rules = (
#规则解析器:将链接提取器提取到的链接进行指定规则(callback)的解析操作
#follow=True:可以将链接提取器继续作用到 连接提取器提取到的链接 所对应的页面中
Rule(link, callback='parse_item', follow=True),
Rule(link_detail,callback='parse_detail')
)
#解析新闻编号和新闻的标题
#如下两个解析方法中不可以实现请求传参!
#如下将两个解析方法解析的数据存储到同一个item中,可以以此存储到两个item
def parse_item(self, response):
#注意:xpath表达式中不可以出现tbody标签
tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
for tr in tr_list:
new_num = tr.xpath('./td[1]/text()').extract_first()
new_title = tr.xpath('./td[2]/a[2]/@title').extract_first()
item = SunproItem()
item['title'] = new_title
item['new_num'] = new_num
yield item
#解析新闻内容和新闻编号
def parse_detail(self,response):
new_id = response.xpath('/html/body/div[9]/table[1]//tr/td[2]/span[2]/text()').extract_first()
new_content = response.xpath('/html/body/div[9]/table[2]//tr[1]//text()').extract()
new_content = ''.join(new_content)
item = DetailItem()
item['content'] = new_content
item['new_id'] = new_id
yield item
#pipelines.py
class SunproPipeline(object):
def process_item(self, item, spider):
#如何判定item的类型
if item.__class__.__name__ == 'DetailItem':
print(item['new_id'],item['content'])
pass
else:
print(item['new_num'],item['title'])
return item
标签:框架,self,item,scrapy,div,response,def
From: https://www.cnblogs.com/vahan/p/16876555.html