5-爬虫-Scrapy爬虫框架环境安装及使用、数据解析、持久化存储、redis数据库的使用、全站数据的爬取
scrapy基本介绍
基本介绍:基于异步爬虫的框架。高性能的数据解析,高性能的持久化存储,全站数据爬取,增量式爬虫,分布式爬虫......
scrapy环境的安装
- Linux:
pip install scrapy
- Windows:
a. pip install wheel
b. 下载twisted https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
- twisted插件是scrapy实现异步操作的三方组件。
c. 进入下载目录,执行 pip install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl
d. pip install pywin32
e. pip install scrapy
scrapy基本使用
- 1.创建一个工程:scrapy startproject proName
- 工程的目录结构:
- spiders:爬虫包/文件夹
- 要求:必须要存储一个或者多页爬虫文件
- settings.py:配置文件
- 2.创建爬虫文件
- cd proName
- scrapy genspider spiderName www.xxx.com
- 3.执行工程
- scrapy crawl spiderName
- 重点关注的日志信息:ERROR类型的日志信息
- settings.py:LOG_LEVEL = 'ERROR'
- settings.py:不遵从robots协议 (将True改为False)
- settings.py:UA伪装
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
spiders下的爬虫文件内容介绍
cd proName
scrapy genspider spiderName www.xxx.com
# -*- coding: utf-8 -*- import scrapy class FirstSpider(scrapy.Spider): #爬虫文件名称:当前爬虫文件的唯一标识 name = 'first' #允许的域名(用来做限定) # allowed_domains = ['www.taobao.com'] #起始的url列表:存储即将要发起请求的url,列表存有的url都会被框架自动进行get请求的发送 start_urls = ['https://www.sogou.com/','https://www.taobao.com'] #数据解析 #参数response就是请求成功后对应的响应对象 def parse(self, response): print(response)
数据解析
- 使用xpath进行数据解析
- 注意:使用xpath表达式解析出来的内容不是直接为字符串,而是Selector对象,想要的
字符串数据是存储在该对象中。
- extract():如果xpath返回的列表元素有多个
- extract_first():如果xpath返回的列表元素只有一个
import scrapy class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.qiushibaike.com/text/'] def parse(self, response): div_list = response.xpath('//*[@id="content"]/div/div[2]/div') for div in div_list: # 返回的并不是字符串形式的作者名称,而是返回了一个Selector对象 # 想要的字符串形式的作者名称是存储在该对象中 # 需要将Selector对象中存储的字符串形式的作者名称取出 # author = div.xpath('./div[1]/a[2]/h2/text()')[0] # 这样取出得是一个对象需要在后边加extract才能取出文本 author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() # Selector.extract():将Selector对象中的字符串取出 # [].extract_first():将列表中的第一个列表元素(Selector)中的字符串取出 content = div.xpath('./a/div/span//text()').extract() # [].extract():将列表中的每一个列表元素Selector对象中的字符串取出 content = ''.join(content) print(author, content)
持久化存储
基于终端指令的持久化存储
只可以将parse方法的返回值存储到制定后缀的文本文件中(只支持这些文本文件类型'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle')
局限性:
- 1.只可以将parse方法返回值进行持久化存储
- 2.只可以将数据存储到文件中无法写入到数据库
存储指令:scrapy crawl spiderName -o filePath
import scrapy class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.qiushibaike.com/text/'] # 基于终端指令进行持久化存储 def parse(self, response): div_list = response.xpath('//*[@id="content"]/div/div[2]/div') all_list = [] for div in div_list: author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() content = div.xpath('./a/div/span//text()').extract() content = ''.join(content) dic = { 'author': author, 'content': content } all_list.append(dic) return all_list
基于管道的持久化存储
1.在爬虫文件中进行数据解析
2.在items.py文件中定义相关的属性
- 属性的个数要和解析出来的字段个数同步
3.将解析出来的数据存储到item类型的对象中
4.将item提交给管道
5.在管道中接收item对象且将该对象中存储的数据做任意形式的持久化存储
6.在配置文件中开启管道机制
qiubai.py
import scrapy from qiubaiPro.items import QiubaiproItem class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.qiushibaike.com/text/'] def parse(self, response): div_list = response.xpath('//*[@id="content"]/div/div[2]/div') all_list = [] for div in div_list: author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() content = div.xpath('./a/div/span//text()').extract() content = ''.join(content) # 实例化item类型的对象 item = QiubaiproItem() # 只能通过中括号调属性,不能用点 item['author'] = author item['content'] = content # 将item提交给管道 yield item
item.py
import scrapy class QiubaiproItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() author = scrapy.Field() content = scrapy.Field()
pipelines.py
class QiubaiproPipeline: fp = None def open_spider(self,spider):#方法只会在爬虫开始时执行一次 print('i am open_spider()') self.fp = open('./qiubai.txt','w',encoding='utf-8') #用来接收item对象,该方法调用的次数取决于爬虫文件像管道提交item的次数 #参数: #- item:就是接收到的item对象 #- spider:爬虫类实例化的对象 def process_item(self, item, spider): author = item['author'] content = item['content'] self.fp.write(author+':'+content) return item def close_spider(self,spider):#在爬虫结束时被执行一次 print('i am close_spider()') self.fp.close()
settings.py
在settings.py中解除该代码得注释
ITEM_PIPELINES = { #300:表示管道的优先级,数值越小优先级越高,优先级越高表示该管道越先被执行 'qiubaiPro.pipelines.QiubaiproPipeline': 300, 'qiubaiPro.pipelines.MysqlPipeline': 301, 'qiubaiPro.pipelines.redisPipeLine': 302, }
管道细节处理
在配置文件中,管道对应的数值表示的是优先级
什么情况下需要使用多个管道类?
- 数据备份。一个管道类表示将数据存储到一种形式的载体中。
想要将数据存储到mysql一份,redis一份,需要有两个管道类来实现。
思考:
- 爬虫文件向管道提交的item只会提交给优先级最高的那一个管道类
- proces_item方法中的return item的作用?
- 将item对象提交给下一个即将被执行的管道类
from itemadapter import ItemAdapter class QiubaiproPipeline: fp = None def open_spider(self, spider): print('打开文件') self.fp = open('./qiubai.txt', 'w', encoding='utf-8') def process_item(self, item, spider): author = item['author'] content = item['content'] self.fp.write(author + ':' + content) return item def close_spider(self, spider): print('关闭文件') self.fp.close() import pymysql #封装一个管道类,将数据存储到mysql中 class MysqlPipeline: conn = None cursor = None def open_spider(self, spider): self.conn = pymysql.Connect( host='127.0.0.1', port=3306, user='root', password='123', charset='utf8', db='spider', ) def process_item(self, item, spider): author = item['author'] content = item['content'] self.cursor = self.conn.cursor() sql = 'insert into qiubai values("%s","%s")' % (author, content) try: self.cursor.execute(sql) self.conn.commit() except Exception as e: print(e) self.conn.rollback() def close_spider(self, spider): self.cursor.close() self.conn.close() import redis class redisPipeline: conn = None def open_spider(self, spider): self.conn = redis.Redis(host='127.0.0.1', port=6379) def process_item(self, item, spider): self.conn.lpush('qiubaiData', item)
redis数据库的使用
redis是一个非关系型数据库
下载安装
中文官网:http://www.redis.cn/
启动
1.启动redis的服务器端
- redis-server
2.启动redis的客户端
- redis-cli
具体使用
查看所有数据:keys *
删除所有数据:flushall
set集合:
- 插入数据: sadd 集合的名称 存储的值
- 查看数据:smembers 集合的名称
- set集合自动去重
list列表
- 插入数据:lpush 列表的名称 插入的值
- 查看数据:lrange 列表名称 0 -1
- 查看长度:llen 列表名称
- 可以存储重复的数据
python中使用需要导包
pip install -U redis==2.10.6
必须下载2.10.6,其他版本不能往里边存字典
import redis class redisPipeline: conn = None def open_spider(self, spider): self.conn = redis.Redis(host='127.0.0.1', port=6379) def process_item(self, item, spider): self.conn.lpush('qiubaiData', item)
全站数据爬取
将所有页码对应的数据进行爬取+存储
- 手动请求发送:
- 通过代码的形式进行请求发送get请求
- yield scrapy.Request(url,callback):
- 可以对指定url发起get请求,回调callback进行数据解析
- 手动发起post请求:
- yield scrapy.FormRequest(url, formdata, callback)
- 问题:start_urls列表中的url是如何发起post请求?
重写父类如下方法即可:
def start_requests(self):
for url in self.start_urls:
yield scrapy.FormRequest(url=url, formdata,callback=self.parse)
duanzi.py
import scrapy from duanziPro.items import DuanziproItem class DuanziSpider(scrapy.Spider): name = 'duanzi' # allowed_domains = ['www.xxx.com'] start_urls = ['https://duanziwang.com/category/一句话段子/1/'] url_model = 'https://duanziwang.com/category/一句话段子/%d/' page = 2 def start_requests(self): for url in self.start_urls: yield scrapy.Request(url=url, callback=self.parse11) def parse11(self, response): article_list = response.xpath('/html/body/section/div/div/main/article') for article in article_list: title = article.xpath('./div[1]/h1/a/text()').extract_first() content = article.xpath('./div[2]/p/text()').extract_first() item = DuanziproItem() item['title'] = title item['content'] = content yield item if self.page < 3: new_url = format(self.url_model %self.page) self.page += 1 yield scrapy.Request(url=new_url, callback=self.parse11)
pipelines.py
from itemadapter import ItemAdapter class DuanziproPipeline: fp = None def open_spider(self, spider): print(spider.name) # duanzi self.fp = open('./duanzi.txt', 'w', encoding='utf-8') def process_item(self, item, spider): self.fp.write(item['title'] + ':' + item['content']+'\n') return item def close_spider(self, spider): self.fp.close()