其他分享
首页 > 其他分享> > 爬虫05--Scrapy框架

爬虫05--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