scrapy 架构介绍
# scrapy:爬虫框架---》使用scrapy创建爬虫项目
# pip install scrapy
# 创建scrapy项目
scrapy startproject 项目名
# 架构
spiders:爬虫,主要是咱们写代码的地方---》设置起始爬取的地址--》解析数据
engine:引擎,大总管,控制数据的整体流动
scheduler:调度器,待爬取的地址,排队,去重
middleware:中间件:下载中间件,爬虫中间件
downloader:下载器,真正下载
pipline:管道,持久化,保存,文件,mysql
# 引擎(EGINE)
引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。
# 调度器(SCHEDULER)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
# 下载器(DOWLOADER)
用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
# 爬虫(SPIDERS)
SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求
# 项目管道(ITEM PIPLINES)
在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
# 下载器中间件(Downloader Middlewares)
位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,你可用该中间件做以下几件事
# 爬虫中间件(Spider Middlewares)
位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)
scrapy 目录结构
myfirstscrapy # 项目名字
-myfirstscrapy # 包
-__init__.py
-spiders # 包 放爬虫,可能会有很多爬虫
-__init__.py
-cnblogs.py # 爬虫文件--》一个爬虫就是一个文件,可以写多个
-items.py # 放一个个类---》类似于django 的models--》模型类
-middlewares.py # 中间件,下载,爬虫中间件
-pipelines.py # 持久化,保存mysql,需要写的位置
-settings.py #配置文件
-scrapy.cfg # 上线会用
# 以后咱们如果写爬虫,写解析,就写 spiders 下的某个py文件 咱么写的最多的
# 以后配置都写在settings 中
# 以后想写中间件:middlewares
# 以后想做持久化:pipelines,items
scrapy爬取和解析
# 1 创建项目:scrapy startproject 项目名
# 2 创建爬虫:scrapy genspider 爬虫名 爬取的地址
scrapy gensipder cnblogs www.cnblogs.com
# 3 运行爬虫
运行cnblgos爬虫---》对首页进行爬取
scrapy crawl 爬虫名字
scrapy crawl cnblogs
scrapy crawl cnblogs --nolog
# 4 快速运行,不用命令
项目根路径新建 run.py,写入如下代码,以后右键运行run.py 即可
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'cnblogs', '--nolog'])
# 5 解析数据---》提供了解析库--》css和xpath
1 response对象有css方法和xpath方法
-css中写css选择器 response.css('')
-xpath中写xpath选择 response.xpath('')
2 重点1:
-xpath取文本内容
'.//a[contains(@class,"link-title")]/text()'
-xpath取属性
'.//a[contains(@class,"link-title")]/@href'
-css取文本
'a.link-title::text'
-css取属性
'img.image-scale::attr(src)'
3 重点2:
.extract_first() 取一个
.extract() 取所有
css解析
def parse(self, response): # css解析 # response 就是爬完后的对象 # print(response.text) # 使用css解析 article_list = response.css('article.post-item') for article in article_list: # 标题 title = article.css('a.post-item-title::text').extract_first() # 摘要 取出所有,单独处理一下 desc = article.css('p.post-item-summary::text').extract() real_desc = desc[0].replace('\n', '').replace(' ', '') if not real_desc: real_desc = desc[1].replace('\n', '').replace(' ', '') # print(real_desc) # 作者:author author = article.css('footer.post-item-foot>a>span::text').extract_first() # print(author) # 头像 image_url = article.css('img.avatar::attr(src)').extract_first() # print(image_url) # 发布日期 date = article.css('span.post-meta-item>span::text').extract_first() # print(date) # 文章地址 url = article.css('a.post-item-title::attr(href)').extract_first() print(''' 文章名:%s 文章摘要:%s 文章作者:%s 作者头像:%s 文章日期:%s 文章地址:%s ''' % (title, real_desc, author, image_url, date, url))
xpath
def parse(self, response): # xpath解析 article_list = response.xpath('//article[@class="post-item"]') for article in article_list: # 标题 title = article.xpath('.//a[@class="post-item-title"]/text()').extract_first() # 摘要 取出所有,单独处理一下 desc = article.xpath('.//p[@class="post-item-summary"]/text()').extract() real_desc = desc[0].replace('\n', '').replace(' ', '') if not real_desc: real_desc = desc[1].replace('\n', '').replace(' ', '') # print(real_desc) # 作者:author # author = article.css('footer.post-item-foot>a>span::text').extract_first() author = article.xpath('.//footer[@class="post-item-foot"]/a/span/text()').extract_first() # print(author) # 头像 # image_url = article.css('img.avatar::attr(src)').extract_first() image_url = article.xpath('.//img[@class="avatar"]/@src').extract_first() # print(image_url) # 发布日期 # date = article.css('span.post-meta-item>span::text').extract_first() date = article.xpath('.//span[@class="post-meta-item"]/span/text()').extract_first() # print(date) # 文章地址 # url = article.css('a.post-item-title::attr(href)').extract_first() url = article.xpath('.//a[@class="post-item-title"]/@href').extract_first() print(''' 文章名:%s 文章摘要:%s 文章作者:%s 作者头像:%s 文章日期:%s 文章地址:%s ''' % (title, real_desc, author, image_url, date, url))
整站爬取cnblogs
parse
def parse(self, response): # xpath解析 article_list = response.xpath('//article[@class="post-item"]') for article in article_list: # 标题 title = article.xpath('.//a[@class="post-item-title"]/text()').extract_first() # 摘要 取出所有,单独处理一下 desc = article.xpath('.//p[@class="post-item-summary"]/text()').extract() real_desc = desc[0].replace('\n', '').replace(' ', '') if not real_desc: real_desc = desc[1].replace('\n', '').replace(' ', '') # print(real_desc) # 作者:author # author = article.css('footer.post-item-foot>a>span::text').extract_first() author = article.xpath('.//footer[@class="post-item-foot"]/a/span/text()').extract_first() # print(author) # 头像 # image_url = article.css('img.avatar::attr(src)').extract_first() image_url = article.xpath('.//img[@class="avatar"]/@src').extract_first() # print(image_url) # 发布日期 # date = article.css('span.post-meta-item>span::text').extract_first() date = article.xpath('.//span[@class="post-meta-item"]/span/text()').extract_first() # print(date) # 文章地址 # url = article.css('a.post-item-title::attr(href)').extract_first() url = article.xpath('.//a[@class="post-item-title"]/@href').extract_first() # print(''' # 文章名:%s # 文章摘要:%s # 文章作者:%s # 作者头像:%s # 文章日期:%s # 文章地址:%s # ''' % (title, real_desc, author, image_url, date, url)) # 1 保存数据 # 2 继续爬取---》下一页 文章详情 # 继续爬取文章详情--->解析和首页解析是不一样的 yield Request(url=url, callback=self.parse_detail) # 继续爬下一页 # yield Request(url=url, callback=self.parse) #callback可以不写 # 解析下一页地址 之css # next_url = 'https://www.cnblogs.com' + response.css('div.pager a:last-child::attr(href)').extract_first() # 解析下一页地址 之xpath next_url = 'https://www.cnblogs.com' + response.xpath('//div[@class="pager"]/a[last()]/@href').extract_first() print(next_url) yield Request(url=next_url, callback=self.parse)
parse_detail
def parse_detail(self, response): print(len(response.text)) # 解析出文章详情--》存html # content=str(response.css('#cnblogs_post_body').extract_first()) # content=str(response.xpath('//div[id="cnblogs_post_body"]').extract_first()) # content=str(response.xpath('//div[contains(@class,"blogpost-body")]').extract_first()) content=str(response.xpath('//div[@id="cnblogs_post_body"]').extract_first()) # 通过id拿不到 print(content)
持久化
# 能实现整站爬取 1 首页---下一页 2 详情页 # 目标是要保存数据 -parse中解析出来文章的一部分内容:标题,作者,摘要。。。缺了文章详情 -parse_detail中有:文章详情- -刚刚爬取的某片文章 跟 文章详情,对应不上,咱们没法合道一起 # 数据传递---》上一个请求中携带数据,传递给下一个响应
数据传递--上一个请求中携带数据,传递给下一个响应
# 上一个解析中,放到request中 item = MyfirstscrapyItem(title=title, real_desc=real_desc, author=author, image_url=image_url, date=date, url=url) yield Request(url=url, callback=self.parse_detail, meta={'item': item}) # 下一个解析中,取出来 item = response.meta.get('item')
持久化数据
在items中新建类
class MyfirstscrapyItem(scrapy.Item): title = scrapy.Field() author = scrapy.Field() real_desc = scrapy.Field() date = scrapy.Field() image_url = scrapy.Field() url = scrapy.Field() # ------文章详情----- content = scrapy.Field()
在解析中,得到item对象,并且yield
# 第一个解析 item = MyfirstscrapyItem(title=title, real_desc=real_desc, author=author, image_url=image_url, date=date,url=url) # 第二个解析 item['content'] = content # yield yield item
配置文件
ITEM_PIPELINES = { "myfirstscrapy.pipelines.MyfirstscrapyFilePipeline": 300, "myfirstscrapy.pipelines.MyfirstscrapyMysqlPipeline": 100, }
pipelines.py
#### 注意爬虫开启和关闭 from itemadapter import ItemAdapter import pymysql class MyfirstscrapyFilePipeline: # 每条记录,都会走这里,如果打开mysql和关闭,都在这个方法中得话 # 我们应该在爬虫开启的时候,打开mysql链接,爬虫关闭的时候,关闭链接 def open_spider(self, spider): print('开了') # 打开文件 self.f = open('cnblogs.txt', 'wt', encoding='utf-8') # 先用文件演示---》 def process_item(self, item, spider): self.f.write(item['title'] + '\n') print('=======', item['title']) self.f.flush() return item def close_spider(self, spider): print('关了') # 关闭文件 self.f.close() class MyfirstscrapyMysqlPipeline: def open_spider(self, spider): self.conn = pymysql.connect( user='root', password="1234", host='127.0.0.1', database='cnblogs', port=3306, ) self.cursor = self.conn.cursor() def process_item(self, item, spider): self.cursor.execute( 'INSERT INTO article (title,author,url,img_url,`date`,real_desc,content)VALUES(%s,%s,%s,%s,%s,%s,%s)', args=[item['title'], item['author'], item['url'], item['image_url'], item['date'], item['real_desc'], item['content']]) self.conn.commit() return item def close_spider(self, spider): self.cursor.close() self.conn.close()
配置文件
#### 基础配置 # 项目名 BOT_NAME = "scrapy_demo" # 爬虫所在路径 SPIDER_MODULES = ["scrapy_demo.spiders"] NEWSPIDER_MODULE = "scrapy_demo.spiders" # 记住 日志级别 LOG_LEVEL='ERROR' # 请求头中的 USER_AGENT USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" # 是否遵循爬虫协议 ROBOTSTXT_OBEY = False # 默认请求头 #DEFAULT_REQUEST_HEADERS = { # "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", # "Accept-Language": "en", #} #爬虫中间件 #SPIDER_MIDDLEWARES = { # "scrapy_demo.middlewares.ScrapyDemoSpiderMiddleware": 543, #} # 下载中间件 #DOWNLOADER_MIDDLEWARES = { # "scrapy_demo.middlewares.ScrapyDemoDownloaderMiddleware": 543, #} # 持久化相关 #ITEM_PIPELINES = { # "scrapy_demo.pipelines.ScrapyDemoPipeline": 300, #} ### 高级配置(提高爬取效率) #1 增加并发:默认16 默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改 CONCURRENT_REQUESTS = 100 值为100,并发设置成了为100 #2 提高日志级别: 在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写: LOG_LEVEL = 'INFO' # 3 禁止cookie: 如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写: COOKIES_ENABLED = False # 4 禁止重试: 对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写: RETRY_ENABLED = False # 5 减少下载超时: 如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写: DOWNLOAD_TIMEOUT = 10 超时时间为10s
标签:url,爬虫,item,scrapy,article,extract,05days,desc From: https://www.cnblogs.com/wzh366/p/18029601