爬虫__scrapy架构介绍__目录结构_爬取和解析,,css解析,,xpath,,整站爬取cnblogs__parse,,parse_Detail,,持久化,持久化数据,,配置文件

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

1
2
3
4
5
6
7
8
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
复制代码

 

posted @   拆尼斯、帕丁顿  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示