大部分的网站展示的数据都进行了分页操作,那么将所有页码对应的页面数据进行爬取就是爬虫中的全站数据爬取。
基于scrapy如何进行全站数据爬取呢?
使用Request方法手动发起请求。
需求:将糗事百科所有页码的作者和段子内容数据进行爬取切持久化存储
spider文件:
class QiubaiSpider(scrapy.Spider):
name = 'qiubai' # 应用名称(唯一标识)
# 允许爬取的域名(如果遇到非该域名的url则爬取不到数据)
allowed_domains = ['https://www.qiushibaike.com/']
# 起始爬取的url
start_urls = ['https://www.qiushibaike.com/text']
# 爬取多页
pageNum = 1 # 起始页码
# 统用的url模板(不可变)
url = 'https://www.qiushibaike.com/text/page/%s/' # 每页的url
# 访问起始URL并获取结果后的回调函数,该函数的response参数就是向起始的url发送请求后,获取的响应对象.该函数返回值必须为可迭代对象或者NUll
def parse(self, response):
# print(response.text) # 获取字符串类型的响应内容
# 获取作者名称和内容
# print(response.body) # 获取字节类型的相应内容
# xpath为response中的方法,可以将xpath表达式直接作用于该函数中
odiv = response.xpath('//div[@class="col1 old-style-col1"]/div')
# print(len(odiv))
content_list = [] # 用于存储解析到的数据
for div_item in odiv:
# xpath函数返回的为列表,列表中存放的数据为Selector类型的数据。
# 我们解析到的内容被封装在了Selector对象中,需要调用extract()函数将解析的内容从Selecor中取出。
author = div_item.xpath('.//div[1]/a[2]/h2/text() | .//div[1]/span/h2/text()')[0].extract()
content = div_item.xpath('.//div[@class="content"]/span/text()').extract()
content = ''.join(content) # 列表转换为字符串
# 打印展示爬取到的数据
# print(author, content)
# print(content)
author = author.strip('\n') # 过滤空行
content = content.strip('\n')
# 将解析到的数据封装至items对象中
item = FirstbloodItem()
item['author'] = author
item['content'] = content
print(author)
yield item # 提交item到管道文件(pipelines.py)
# 爬取所有页码数据
print('pageNum={}'.format(self.pageNum))
if self.pageNum < 5: # 一共爬取13页(共13页)
self.pageNum += 1
new_url = format(self.url % self.pageNum)
print(new_url)
# 递归爬取数据:callback参数的值为回调函数(将url请求后,得到的相应数据继续进行parse解析),递归调用parse函数
# 在 scrapy.Request() 函数中将参数 dont_filter=True 设置为 True,使 requests 不被过滤:
yield scrapy.http.Request(url=new_url, callback=self.parse, dont_filter=True)
items.py文件
import scrapy
class FirstbloodItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
author = scrapy.Field()
content = scrapy.Field()
pass
pipeline文件
import pymysql
class FirstbloodPipeline(object):
# 构造方法
def __init__(self):
self.fp = None # 定义一个文件描述符属性
# 下列都是在重写父类的方法:
# 开始爬虫时,执行一次
def open_spider(self, spider):
print('爬虫开始')
self.fp = open('./data.txt', 'w',encoding='utf-8')
# 专门处理item对象
# 因为该方法会被执行调用多次,所以文件的开启和关闭操作写在了另外两个只会各自执行一次的方法中。
def process_item(self, item, spider):
author = item['author']
content = item['content']
contents = '{}:{}\n\n'.format(author,content)
self.fp.write(contents)
return item # 传递到下一个被执行的管道类
def close_spider(self, spider):
print('爬虫结束')
self.fp.close()
#管道文件中的一个管道类对应的是讲数据存储到一种平台
#爬虫文件提交的item只会给管道文件中第一个被执行的管道类接受
#process_item的return item表示item传递到下一个被执行的管道类
#将数据存储到数据库
class mysqlPipeline(object):
# 构造方法
def __init__(self):
self.conn = None # 定义一个文件描述符属性
self.cursor = None
self.num = 0
# 下列都是在重写父类的方法:
# 开始爬虫时,执行一次
def open_spider(self, spider):
self.conn = pymysql.Connect(host='192.168.31.xx',port=3306,user='root',password='111',db='xxx_db',charset='utf8')
print('爬虫数据库开始')
# 专门处理item对象
# 因为该方法会被执行调用多次,所以文件的开启和关闭操作写在了另外两个只会各自执行一次的方法中。
def process_item(self, item, spider):
author = item['author']
content = item['content']
self.cursor = self.conn.cursor()
try:
self.cursor.execute('insert into qiubai values(%s,%s)' ,(author,content))
self.conn.commit()
except Exception as e:
print(e)
self.conn.rollback()
return item
def close_spider(self, spider):
print('爬虫数据库结束')
self.cursor.close()
self.conn.close()
setting
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' # 伪装请求载体身份
# Obey robots.txt rules
# ROBOTSTXT_OBEY = True
ROBOTSTXT_OBEY = False #可以忽略或者不遵守robots协议
#只显示指定类型的日志信息
LOG_LEVEL='ERROR'
# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'firstBlood.pipelines.FirstbloodPipeline': 300,
'firstBlood.pipelines.mysqlPipeline': 320,
#300表示优先级,越小越高
}