在爬虫框架篇二中,已经将数据爬取下来了,接下来要做的就是数据的解析处理与持久化
目录
1、确定数据,编辑items.py
2、解析数据,修改parse()方法
3、在settings类中开启pipeline管道
4、修改pipelines.py,数据持久化
5、运行爬虫查看结果
6、遇到的一些错误
1、确定需要的数据
需要爬取的信息有:
主页中:
电影名称、
电影链接、
子链接中:
导演、
地区、
上映日期
为需要爬取的信息定义对应的属性,编辑items.py
item
import scrapy
# itmes对象是种简单的容器,可以理解为dict,保存了爬虫程序得到的数据。
# 定义我们获取信息的字段
class Movie80SItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# 主页面中的电影标题和子链接
moviename = scrapy.Field() # 电影名称
movielink = scrapy.Field() # 电影链接
# 子链接中的导演、地区、上映日期、豆瓣评分
moviedirector = scrapy.Field() # 导演
movieregion = scrapy.Field() # 地区
movieReleasedate = scrapy.Field() # 上映日期
2、在爬虫文件中解析需要的数据
爬虫文件:
image.pngspider
# -*- coding: utf-8 -*-
import scrapy
# 导入Movie80SItem类
from movie80s.items import Movie80SItem
class Movie80Spider(scrapy.Spider):
name = 'movie80' # 应用名称
# 允许爬取的域名,需要手动修改(如果遇到非该域名的url则爬取不到数据,但是对于start_urls里的起始爬取页面,它是不会过滤的)
allowed_domains = ['80s.tw']
start_urls = ['https://www.80s.tw/movie/list/'] # 起始爬取的url
# 访问起始URL并获取结果后的回调函数,该函数的response参数就是向起始的url发送请求后,获取的响应对象.该函数返回值必须为可迭代对象或者NUll
def parse(self, response):
# 解析主页面中的电影标题和子链接
ul = response.xpath('//ul[@class="me1 clearfix"]/li')
for li in ul:
# 创建Movie80SItem类
item = Movie80SItem()
item['movielink'] = "https://www.80s.tw{}".format(li.xpath('./h3/a/@href').extract()[0].strip())
item['moviename'] = li.xpath('./h3/a/text()').extract()[0].strip()
# 为什么使用yield?好处?
# 让整个函数变成一个生成器。每次遍历的时候挨个读到内存中,不会导致内存的占用量瞬间变高
yield scrapy.Request(url=item['movielink'], callback=self.parse_detail, meta={'item': item}, dont_filter=False)
# 爬取所有页码数据
next_page = response.xpath('//a[text()="下一页"]/@href').extract()[0].strip()
if next_page:
next_page = "https://www.80s.tw{}".format(next_page)
yield scrapy.Request(url=next_page,
callback=self.parse, # callback参数的值为回调函数(将url请求后,得到的相应数据继续进行parse解析),此处是调用parse函数本身
# meta={"item": item}, # meta实现将item数据传递给callback调用的函数,此处是传递给parse函数本身
dont_filter=False # 是否允许网址重复
)
# 解析子链接中的导演、地区、上映日期、豆瓣评分
def parse_detail(self, response):
item = response.meta['item'] # 注意这里实例化的是meta的,是parse函数传递过来的第二层内容
item['moviedirector'] = response.xpath('//div[@class="info"]/div[1]/span[4]/a/text()').extract()[0].strip() # 导演
item['movieregion'] = response.xpath('//div[@class="info"]/div[1]/span[2]/a/text()').extract()[0].strip() # 地区
item['movieReleasedate'] = response.xpath('//div[@class="info"]/div[1]/span[5]/text()').extract()[0].strip() # 上映日期
# 提交item到管道文件(pipelines.py)
yield item
将获取的数据交给pipeline
3、在settings类中开启pipeline管道
修改内容及其结果如下:
settings
# 配置使用Pipeline
# 这里设置了两个item管道
ITEM_PIPELINES = {
'movie80s.pipelines.Movie80SPipeline': 300, # 300表示为优先级,值越小优先级越高
'movie80s.pipelines.Movie80SPipeline1': 200,
}
# 防止被ban,设置访问间隔时间,意思是,每3秒请求一次。
DOWNLOAD_DELAY = 3
4、pipeline管道对数据进行持久化操作
pipelines.py文件获得item后将会调用管道函数来对item进行处理,这里将数据存入text文件中
在settings配置中设置了两个管道,则在pipelines.py文件中也需要设置两个类。
Item Pipeline
# 代码如下
from itemadapter import ItemAdapter
# 该类为管道类,该类中的process_item方法是用来实现持久化存储操作的。
class Movie80SPipeline:
# 构造方法
def __init__(self):
self.fp = None # 定义一个文件描述符属性
self.filename = "movie80.txt" # 定义保存数据的文件名称
# 下列都是在重写父类的方法:
# 开始爬虫时,执行一次
def open_spider(self, spider):
print('爬虫开始____')
self.fp = open('./{}'.format(self.filename), 'w')
# 该方法会执行多次,所以文件的开启和关闭操作写在了另外两个只会各自执行一次的方法中。
def process_item(self, item, spider):
# 将爬虫程序提交的item进行持久化存储
self.fp.write("{}, {}, {}, {}, {}\n".format(
item['moviename'], item['movielink'], item['moviedirector'], item['movieregion'], item['movieReleasedate']))
# 优先级最低的管道类的return会在控制台输出原item数据,可以选择不写
# return item
# 结束爬虫时,执行一次
def close_spider(self, spider):
self.fp.close()
print('爬虫结束____')
# 如果想实现另一种形式的持久化操作,则可以再定制一个管道类:
class Movie80SPipeline1:
def process_item(self, item, spider):
# 优先级高的管道类这里必须返回item,否则在优先级低的管道类中接收不到item会报错
return item
5、运行爬虫查看结果
结果如下:
image.png image.png6、遇到的一些错误
6.1、设置了翻页爬取,但scrapy明显没有执行翻页
image.png第一种方法(不推荐):
百度了一些答案,普遍是是将dont_filter=True
设置前:
yield scrapy.Request(url=next_page,
callback=self.parse, # callback参数的值为回调函数(将url请求后,得到的相应数据继续进行parse解析),此处是调用parse函数本身
# meta={"item": item}, # meta实现将item数据传递给callback调用的函数,此处是传递给parse函数本身
dont_filter=True # 是否允许网址重复
)
设置后:
yield scrapy.Request(url=next_page,
callback=self.parse, # callback参数的值为回调函数(将url请求后,得到的相应数据继续进行parse解析),此处是调用parse函数本身
# meta={"item": item}, # meta实现将item数据传递给callback调用的函数,此处是传递给parse函数本身
dont_filter=False # 是否允许网址重复
)
第二种方法:
第一种方法设置可以解决问题,但是如果网址重复了,就有可能爬取到重复的数据。看了一些官方资料后,发现dont_filter和allowed_domains域名限制也有点关系,从这个思路出发,我发现修改一下allowed_domains这个地方就好了
修改前:
allowed_domains = ['https://www.80s.tw/movie/list/']
start_urls = ['https://www.80s.tw/movie/list/'] # 起始爬取的url
修改后
allowed_domains = ['80s.tw']
start_urls = ['https://www.80s.tw/movie/list/'] # 起始爬取的url
ok
6.2、设置了爬取子链接,但未进入callback函数中
这个问题是我设置了爬取子链接中的一些内容,callback的url是子链接,但设置后,并没有进入设置的callback函数中
image.png这个错误我找了几个答案,都说start_urls起始爬取的url需要是列表,像这样:
start_urls = ['https://www.80s.tw/movie/list/']
但明显我的问题不是出在这里,
之后发现在返回的子链接前加上https:的判别
加之前:
item['movielink'] = "www.80s.tw" + li.xpath('./h3/a/@href').extract()[0].strip()
item['moviename'] = li.xpath('./h3/a/text()').extract()[0].strip()
# 提交item到管道文件(pipelines.py)
# 为什么使用yield?好处?
# 让整个函数变成一个生成器。每次遍历的时候挨个读到内存中,不会导致内存的占用量瞬间变高
yield scrapy.Request(url=item['movielink'], callback=self.parse_detail, meta={'item': item})
加之后:
item['movielink'] = "https:www.80s.tw" + li.xpath('./h3/a/@href').extract()[0].strip()
item['moviename'] = li.xpath('./h3/a/text()').extract()[0].strip()
# 提交item到管道文件(pipelines.py)
# 为什么使用yield?好处?
# 让整个函数变成一个生成器。每次遍历的时候挨个读到内存中,不会导致内存的占用量瞬间变高
yield scrapy.Request(url=item['movielink'], callback=self.parse_detail, meta={'item': item})
OK
6.3、item数据只有最后一条
只显示主页数据中爬到的最后一条数据,其他都正常
image.png这是我对标签进行遍历时,将item对象放置在了for循环的外部。修改代码就好
修改前:
image.png修改后:
image.png成功
image.png传送门
爬虫框架scrapy篇一——scrapy的架构
https://www.jianshu.com/p/39b326f9cad6
爬虫框架scrapy篇二——创建一个scrapy项目
https://www.jianshu.com/p/00d99a9628b0
爬虫框架scrapy篇四——数据入库(mongodb,mysql)
https://www.jianshu.com/p/573ca74c2277
爬虫框架scrapy篇五——其他操作:post翻页请求
https://www.jianshu.com/p/bca689b4ebbd
参考:
Python关键字yield的解释
使用scrapy爬数据遇到的那些error坑~~
scrapy的一些容易忽视的点(模拟登陆,传递item等)
网友评论