ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

爬虫05--Scrapy框架

2022-06-13 00:32:27  阅读:226  来源: 互联网

标签:05 -- 爬虫 item Scrapy print article response scrapy


1 scrapy 介绍安装

# 1.介绍
通用的网络爬虫框架, 爬虫界的django,也可用于如数据挖掘、监测和自动化测试等领域

Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架 (性能比较高的框架)。
因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。


# 2.安装
mac/linux:  pip3 install scrapy  # 直接安装

windows:  # 少部分人成功不了
    1.pip3 install wheel  
    # 安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
    
    2.pip3 install lxml
    3.pip3 install pyopenssl
    4.下载并安装pywin32:
        https://github.com/mhammond/pywin32/releases
        
    5.下载twisted的wheel文件:
        http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
            
    6.执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
    7.pip3 install scrapy

2 scrapy 架构目录

# 整体架构

# 5大组件
  爬虫(SPIDERS):开发人员自定义的类,用来解析responses和提取items,或者发送新的请求request
  引擎(EGINE):大总管,负责控制数据的流向
  调度器(SCHEDULER):由它来决定下一个要抓取的网址是什么,去重
    -深度优先 (先将本链接所有层爬取完,再顶层的页面)
    -广度优先 (先将本页面所有内容爬取完,再下一层)
    
  下载器(DOWLOADER):用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的

  项目管道(ITEM PIPLINES):在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
            
# 2大中间件
  爬虫中间件(Spider Middlewares):位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入和输出(用的很少)
    
  下载中间件(Downloader Middlewares):引擎和下载器之间,加代理,加头,集成selenium        
        
# 开发者只需要在固定的位置写固定的代码即可(写的最多的SPIDERS、ITEM PIPLINES 和 Downloader Middlewares)    

# 目录介绍
firstscrapy    # 项目名字
  -firstscrapy  # 包
    -spiders   # 所有的爬虫文件放在里面
        -baidu.py    # 一个个的爬虫(以后基本上都在这写东西)
        -chouti.py
    -middlewares.py  # 中间件(爬虫/下载中间件 都写在这)
    -pipelines.py    # 持久化相关写在这(items.py中类的对象)
    -items.py        # 一个一个的类,相当于models.py
    -settings.py     # 配置文件
    -main.py         # 自己加的,执行爬虫
    
  -scrapy.cfg         # 上线部署相关

3 scrapy 基本使用

# 1 新建项目
  scrapy startproject 项目名

  # eg: scrapy startproject firstscrapy
    
    
# 2 创建爬虫(创建app)  进入到项目中 运行
  scrapy genspider 爬虫名 爬虫地址

  # eg: scrapy genspider chouti dig.chouti.com
  一执行就会在spider文件夹下创建出一个py文件,名字叫chouti
    
    
# 3 运行爬虫
  scrapy crawl chouti   # 执行爬虫,打印日志
  scrapy crawl chouti --nolog  # 执行爬虫,不打印日志
    
    
# 4 支持右键执行爬虫 (使用脚本运行爬虫)
  -在项目路径下新建一个main.py
    from scrapy.cmdline import execute
	execute(['scrapy','crawl','chouti','--nolog'])

4 scrapy 解析数据(***)

#  重点

# 1 response对象有css方法和xpath方法  # 或者 自己使用第三方 bs、lxml等来解析
  response.selector.css()、response.selector.xpath()
  可简写为
  response.css('css选择器')
  response.xpath('xpath选择器')
    
    
# 2 Xpath 和 css选择器 获取文本和属性
# xpath取文本内容   /text()
  .//a[contains(@class,"link-title")]/text()

# xpath取属性    /@属性名
  .//a[contains(@class,"link-title")]/@href

# css取文本   ::text
  a.link-title::text

# css取属性    ::attr(属性名)
  img.image-scale::attr(src)
    
    
# 3 从selector对象中解出内容
选择器解析后是标签元素,内容需要提取出来

  .extract_first()  # 取一个  返回是字符串
  .extract()        # 取所有  返回是列表形式

4.1 案例--cnblogs.py

# eg: 获取博客园 首页的 新闻标题、摘要、头像等

import scrapy
class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'   # 爬虫名
    allowed_domains = ['www.cnblogs.com'] # 允许爬取的域
    start_urls = ['http://www.cnblogs.com/'] # 爬取的起始地址

    def parse(self, response):  # 响应对象response,从中解析出想要的数据
        # print('--------------',response)
        # print(response.text)  # 可以使用bs4解析
        
        ############ css
        article_list=response.css('.post-item')  # 查出所有类名为post-item的标签,取多条
        # print(len(article_list))
        for article in article_list:
            title=article.css('div.post-item-text>a::text').extract_first()  # 取一条
            # article.css('section>div>a::text')
            href=article.css('div.post-item-text>a::attr(href)').extract_first()
            author=article.css('a.post-item-author>span::text').extract_first()
            desc=article.css('p.post-item-summary::text').extract_first()
            # 取不到就是None
            photo=article.css('p.post-item-summary>a>img::attr(src)').extract_first()

            print(title)
            print(href)
            print(author)
            print(desc)
            print(photo)
            print('----------')

        ########### xpath
        article_list = response.xpath('//*[@class="post-item"]')  # 查出所有类名为post-item的标签,取多条
        # print(len(article_list))
        for article in article_list:
            # 注意:使用 . 从当前标签下找
            title = article.xpath('.//a[@class="post-item-title"]/text()').extract_first()  # 取一条
            href = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
            author=article.xpath('.//a[@class="post-item-author"]/span/text()').extract_first()
            desc=article.xpath('.//div[@class="post-item-text"]/p/text()').extract_first()
            photo=article.xpath('.//p//img[@class="avatar"]/@src').extract_first()
            # photo = article.xpath('./section/div/p/a/img/@src').extract_first()

            print(title)
            print(href)
            print(author)
            print(desc)
            print(photo)
            print('----------')

5 setting 相关配置

5.1 基本配置

# 1 是否遵循爬虫协议
ROBOTSTXT_OBEY = False

# 2 爬虫中间件
SPIDER_MIDDLEWARES = {
    'myfirst_crawl.middlewares.MyfirstCrawlSpiderMiddleware': 543,
}

# 3 下载中间件
DOWNLOADER_MIDDLEWARES = {
    'myfirst_crawl.middlewares.MyfirstCrawlDownloaderMiddleware': 543,
}

# 4 请求客户端类型 (用户代理)
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'

# 5 日志级别 (比--nolog好在,如果出错,控制台会报错)
LOG_LEVEL='ERROR'   # 默认是 INFO ,上线改成 ERROR

5.2 提高爬虫效率

# 1 增加并发:
  默认scrapy开启的并发线程为16个,可以适当进行增加。
  在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
    
# 2 降低日志级别:
  在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。
  可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = 'ERROR'
    
# 3 禁止cookie:
  如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。
  在配置文件中编写:COOKIES_ENABLED = False
    
# 4 禁止重试:
  对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。
  在配置文件中编写:RETRY_ENABLED = False
    
# 5 减少下载超时:
  如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。
  在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s

6 持久化方案(***)

# 从内存写到硬盘上的过程就叫持久化
爬取了页面----》清洗出数据了----》写到硬盘上(文件,mysql)

6.1 重点

# 通用方法(pipline)

1 在items.py中写一个类,继承scrapy.Item  # 相当于 装菜的盘子,
    class CnblogsArticleItem(scrapy.Item):

2 在类中写类属性
    title = scrapy.Field()
    
3 在爬虫中导入item类,实例化得到对象,把要保存的数据放到item对象中
    item = CnblogsArticleItem()
    item['title'] = title  # 把数据放进盘子中
    在最后 yield item  # 每条数据构建完之后,若不把item yield出来,循环获取完成后,item数据就变成只是最后一条数据
    
4 修改配置文件,指定pipline,可以指定多个 (真正存数据的地方 数字表示优先级,越小越高)  # 指定盘子的菜,放在哪 (文件、数据库)
    ITEM_PIPELINES = {
        'crawl_cnblogs.pipelines.CrawlCnblogsPipeline': 300,
        ...
    }
    
5 写一个pipline类:CrawlCnblogsPipeline

    open_spider(self, spider):  # spider开始运行前,执行
        数据初始化,打开文件,打开数据库链接
        self.f=open()  # 同一个类中,其他方法要使用该变量,可放在对象中

    process_item(self, item, spider):
        真正存储的地方
        # 若是打开文件或数据库连接的操作,放进该函数,会一直重复打开,消耗资源
        # 也可能会导致数据只是最后一条 (因为文件重复打开后,进行了覆盖操作) 
        
        self.f.write()
        return item  # 一定不要忘了return item,交给后续的pipline继续使用

    close_spider(self, spider): # spider运行结束后,执行
        销毁资源,关闭文件,关闭数据库链接
        self.f.close()

7 全栈爬取cnblogs文章

# 爬取下一页:
  解析出来,yield Request(next),就会继续爬取

# 爬取文章详情
  解析出地址
    详情获取回来的解析函数,不一样

7.1 request和response对象传递参数

# 1 参数存放在request对象中   # 请求地址  回调执行函数  传递参数
  Request(url=url, callback=self.parse_detail,meta={'item':item})  
    
# 2 在response对象中,获取
  item=response.meta['item']

7.2 解析出下一页地址,并继续爬取

next='https://www.cnblogs.com'+response.css('.pager a:last-child::attr(href)').extract_first()
print(next)

# yield Request(url=next,callback=self.parse)
yield Request(url=next)

7.3 解析出详情页地址,并使用其他解析函数爬取

def parse(self, response):
    article_list = response.xpath('//*[@class="post-item"]')
    
    for article in article_list:
        href = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
        item['href'] = href
        
        # 抛出requests对象,让解析器继续去解析爬取  且可传递参数 item对象
        yield Request(url=href, callback=self.parse_detail, meta={'item': item})


def parse_detail(self, response):
    item = response.meta['item']
    content = response.css('#cnblogs_post_body').extract_first()

    item['content'] = content
    # print('文章标题:', item['title'])
    yield item  # 抛出item对象,让item Pipepine去存数据

8 爬虫中间件和下载中间件

# 1 爬虫和下载中间件要使用,需要在配置文件中配置

SPIDER_MIDDLEWARES = {
  'crawl_cnblogs.middlewares.CrawlCnblogsSpiderMiddleware': 5,
}
DOWNLOADER_MIDDLEWARES = {
  'crawl_cnblogs.middlewares.CrawlCnblogsDownloaderMiddleware': 5,
}

8.1 下载中间件的定义

# 1 写在middelwares.py中,写个类

# 2 类中写方法
-process_request(self, request, spider):
  
    -返回 None,继续进入下一个中间件
    -返回 request对象,会被引擎放到调度器,等待下一次被调度执行
    -返回 response对象,会被引擎放到spider中,解析数据
    
    -这里可以干什么事?
        -修改请求头
        -修改cookie
        -加代理
        -加selenium

-process_response(self, request, response, spider)

8.2 下载中间件的应用

# 应用:给requests对象 添加 cookie、ip代理、header 和 集成selenium

0 在下载中间件的process_reqeust方法中

# 1 加cookie
    request.cookies['name']='lqz'
    request.cookies= {}
    
# 2 修改header (token认证、ua等)
    request.headers['Auth']='asdfasdfasdfasdf'
    request.headers['USER-AGENT']='ssss'
    
# 拓:fake_useragent模块,可以随机生成user-aget
    from fake_useragent import UserAgent
    ua = UserAgent()
    print(ua.ie)      # 随机打印ie浏览器任意版本
    print(ua.firefox) # 随机打印firefox浏览器任意版本
    print(ua.chrome)  # 随机打印chrome浏览器任意版本
    print(ua.random)  # 随机打印任意厂家的浏览器 
    
        
# 3 加代理(从代理池中获取)
    request.meta['proxy']='http://103.130.172.34:8080'
    
    
# 4 集成selenium  (因为有的页面,需要js才加载数据,用原来的下载器不会执行,使用selenium就会执行js,数据更全)
-在爬虫类中类属性
    driver = webdriver.Chrome(executable_path='')
    
-在爬虫类中方法:
    def close(spider, reason):
        spider.driver.close()
        
-在中间件中的process_reqeust中
    from scrapy.http import HtmlResponse
    spider.driver.get(url=request.url)  # selenium get访问url
    
    # 返回scrapy的response对象(url, body,request)
    response=HtmlResponse(url=request.url,body=spider.driver.page_source.encode('utf-8'),request=request)
    return response

9 源码分析--去重规则

9.1 scrapy源码分析--去重

目的:爬过一个url地址后,后期如果再爬取该地址,就不爬了---》使用集合

# 1 使用了集合去重  (元素可存可取)

# 2 默认使用的去重类:
DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'

# 3 后期你可以自己写一个类,替换掉内置的去重
  -自定义去重类:
    # 继承BaseDupeFilter,重写 request_seen()
    def request_seen(self, request):
        fp = self.request_fingerprint(request)  # 将请求进行生成指纹
    	-返回True,就不爬了
    	-返回False,就继续爬取

  -使用布隆过滤器 BloomFilter:
    极小内存,快速校验是否重复
	
    
# 4 生成指纹,会把下面两种地址生成一样的指纹(本质是把?后面的参数排序,再生成指纹)
  www.baidu.com?name=lqz&age=18
  www.baidu.com?age=18&name=lqz
  # 上面两个地址,若是直接放进集合中,会判定成两个地址,但实质,这两个是同一个地址请求  

9.2 布隆过滤器

# 布隆过滤器:极小内存,快速校验是否重复,
    元素可存,不可取,只用来判断一个元素是否在一个集合中
    
# 原理:
  BloomFilter 会开辟一个m位的bitArray(位数组),开始所有数据全部置 0。
  当一个元素过来时,能过多个哈希函数(h1,h2,h3....)计算不同的在哈希值,
  并通过哈希值找到对应的bitArray下标处,将里面的值 0 置为 1 。
  关于多个哈希函数,它们计算出来的值必须 [0,m) 之中。 
                             
  当来查找对应的值时,同样通过哈希函数求值,再去寻找数组的下标,
  如果所有下标都为1时,元素存在。当然也存在错误率。
 (如:当数组全部为1时,那么查找什么都是存在的),
  但是这个错误率的大小,取决于数组的位数和哈希函数的个数

# 博客地址:
https://www.cnblogs.com/xiaoyuanqujing/protected/articles/11969224.html
https://developer.aliyun.com/article/773205

                       
########### python中使用布隆过滤器

# 安装
# https://www.lfd.uci.edu/~gohlke/pythonlibs/#bitarray
pip3 install bitarray-xx.whl
pip3 install pybloom-live


# 示例一:
# ScalableBloomFilter 可以自动扩容  常用
from pybloom_live import ScalableBloomFilter

bloom = ScalableBloomFilter(initial_capacity=100, error_rate=0.001,mode=ScalableBloomFilter.LARGE_SET_GROWTH)

url = "www.cnblogs.com"
url2 = "www.liuqingzheng.top"
bloom.add(url)
bloom.add(url2)

print(url in bloom)
print(url2 in bloom)

                       
# 示例二:
# BloomFilter  定长
from pybloom_live import BloomFilter

bf = BloomFilter(capacity=1000)
url='www.baidu.com'
bf.add(url)

print(url in bf)
print("www.liuqingzheng.top" in bf)

9.3 redis实现布隆过滤器

# 需要编译安装 redis 插件 Redisbloom

# 使用redis实现布隆过滤器
import redis
client = redis.Redis()
size = 10000
count = 0
client.execute_command("bf.reserve", "lqz", 0.001, size)  # 新增
for i in range(size):
    client.execute_command("bf.add", "lqz", "xxx%d" % i)
    result = client.execute_command("bf.exists", "lqz", "xxx%d" % (i + 1))
    if result == 1:
        print(i)
        count += 1
print("size: {} , error rate: {}%".format(size, round(count / size * 100, 5)))

10 scrapy-redis实现分布式爬虫

https://www.cnblogs.com/liuqingzheng/p/16005882.html

# 分布式: 将整个任务,分成多个步骤,每个步骤实现不同的小任务,并部署在多个机器上。


# scrapy 实现分布式爬虫的原理:
  原来scrapy的Scheduler维护的是本机的任务队列(存放Request对象及其回调函数等信息)
    +本机的去重队列(存放访问过的url地址),
  所以实现分布式爬取的关键就是,找一台专门的主机上运行一个共享的队列比如Redis,
  然后重写Scrapy的Scheduler,让新的Scheduler到共享队列存取Request,并且去除重复的Request请求,

所以总结下来,实现分布式的关键就是三点:
  1.共享队列
  2.重写Scheduler,让其无论是去重还是任务都去访问共享队列
  3.为Scheduler定制去重规则(利用redis的集合类型)


# 1.安装scrapy-redis


# 2 在配置文件中添加配置
# 使用scrapy_redis的Scheduler替换掉原来的Scheduler
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 使用scrapy_redis的RFPDupeFilter替换原来的去重规则
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# piplie,持久化的方法
ITEM_PIPELINES = {   # 数字越小,优先级越高
   'scrapy_redis.pipelines.RedisPipeline': 400,  # 将分布式存储到redis中
   # 'myfirst_crawl.pipelines.MySqlPipeline': 300,
   'myfirst_crawl.pipelines.FilePipeline': 200,
}

# 默认就是本地(可以不写)
# REDIS_HOST = 'localhost'
# REDIS_PORT = 6379


# 3.改造spider  在原来的基础上,爬虫类继承RedisSpider
from scrapy_redis.spiders import RedisSpider
class CnblogsSpider(RedisSpider):
    name = 'cnblogs_redis'  # 爬虫名
    allowed_domains = ['www.cnblogs.com']  # 允许爬取的域
    redis_key = 'myspider:start_urls'  # 设置redis中 存取起始页的key
    # 以后爬虫启动后,并不会开始爬取,因为没有起始地址
    # 起始地址 需要自己手动写到redis中:redis-cli lpush myspider:start_urls http://www.cnblogs.com/
    
    
# 4.在不同机器上启动 (并不会立马开始,因为没有给定起始页)
scrapy crawl cnblogs_redis

# 5.在redis中写入:
redis-cli lpush myspider:start_urls http://www.cnblogs.com/
        
  
# 以后关闭任何一个机器节点,都不影响,即便重启,接着之前的继续爬取

# 总源码不到1000行,有兴趣可以看

11 爬虫其他知识

1 抓包工具:fiddler(windows),青花瓷(Mac),抓http的包,发http包,模拟网络慢 

2 抓手机包

3 js逆向,加密方式一定是js写的 
  原理:就是在前端中,一点点倒退找到网站的该js加密方法,调用它的,或者自己重新模仿它,写一个使用

    username=2333&password=111--js加密-->asdfasdfasdfasfdasfasdf
    
    
4 手机端爬虫
    -appnium---》类似于selenium---》操作手机模拟人的行为
    
    -app端,是java加密---》对apk进行反编译成java代码---》看懂加密算法
      重写加密,传入你想要的值,得到可以请求的值,发送http请求
    
    -app端,用java代码调用c语言代码写的 so,dll库 (动态链接库) 加密
      反编译--》java.xxx()--->动态调试---》把so反编译---》汇编语言--->动态看数据变化---》猜逻辑

补充

# mysql 编码
    -utf8:mysql的utf8格式,不是真正的utf-8,它是mysql自己定制的格式,使用两个字节存储一个字符,存表情文字,存不进去,报错了
    -utf8mb4:才是真正的utf-8,使用最大四个字节存储一个字符
    
# 关于编码问题
    http://www.liuqingzheng.top/others/1/01-ASCII%E7%A0%81%EF%BC%8CUnicode%E5%92%8CUTF-8%E7%BC%96%E7%A0%81/
        
        
# 面试题
1 python 的函数参数传递是值传递还是引用传递
    -分类型:
        -如果是可变类型,引用
        -如果是不可变类型,copy了一份参数,传递

    -可变类型和不可变类型,其他语言没有这个东西

标签:05,--,爬虫,item,Scrapy,print,article,response,scrapy
来源: https://www.cnblogs.com/Edmondhui/p/16369378.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有