Scrapy

简介

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。我们只需要少量代码,就能够快速的抓取。
Scrapy使用了Twisted[‘twisted’]异步网络框架,可以加快我们的下载速度。

Scrapy的工作流程

在这里插入图片描述

模块功能实现方式
Scrapy Engine(引擎)总指挥:负责数据和信号的在不同模块间传递scrapy框架实现
Scheduler (调度器)一个队列,存放引擎发过来的request请求scrapy框架实现
Downloader (下载器)下载引擎发送来的requests请求,并返回引擎scrapy框架实现
Spider (爬虫)处理引擎发来的response,提取数据,提取url,并交给引擎需要手写
Item Pipeline (管道)处理引擎传过来的数据,比如存储需要手写
Downloader Middlewares (下载器中间件)可以自定义的下载扩展中间件,比如设置代理一般不用手写
Spider Middlewares (爬虫中间件)可以自定义request请求和进行response过滤一般不用手写

Scrapy创建项目

# 安装scrapy
pip install Scrapy
# 创建项目
scrapy startproject projectname
# 创建爬虫
scrapy genspider spidername spider域名
# 提取数据,完善spider。
# 保存数据,pipeline中保存数据。
# 启动爬虫
scrapy crawl spidername

Scrapy项目目录结构

在这里插入图片描述

Scrapy编写爬虫

爬虫文件

创建爬虫之后,会在项目中spiders目录下生成爬虫名的py文件。我们要对这个文件进行完善。编写数据爬取方式。
itcast.py

import scrapy


class ItcastSpider(scrapy.Spider):
    name = 'itcast'   # 定义的爬虫名
    allowed_domains = ['itcast.cn']  # 允许爬取的域名范围
    start_urls = ['http://www.itcast.cn/channel/teacher.shtml']  # 开始爬取的url

    def parse(self, response):
    	# 数据解析方法。方法名不能修改。
        li_list = response.xpath("//div[@class='tea_con']//li")
        for li in li_list:
            item = {}
            item["name"] = li.xpath(".//h3/text()").extract_first()  
            item["title"] = li.xpath(".//h4/text()").extract()[0]
            # extract_first()  == extract()[0]
            yield item

从选择其中提取字符串:

  • extract() 返回一个包含有字符串数据的列表。
  • extract_first() 返回列表的第一个字符串。同extract()[0]

注意:

  • spider中的parse方法名不能修改。
  • 需要爬去的URL地址必须要属于allow_domain下的链接。start_urls 首次爬取的时候,可以不满足此条件。
  • response.xpath()返回的是一个包含selector对象的列表。
  • scrapy中的response没有content属性,使用body替代。response.body是二进制信息,使用时需要decode()转码。

spider的数据传到pipline使用yield。让整个函数变成一个生成器,减少内存占用。

爬虫构造请求

很多时候,爬虫需要从页面中提取出 URL,并爬取该URL中的内容。这时候就需要构造请求。

class ItcastSpider(scrapy.Spider):
    name = 'itcast'   # 定义的爬虫名
    allowed_domains = ['itcast.cn']  # 允许爬取的域名范围
    start_urls = ['http://www.itcast.cn/channel/teacher.shtml']  # 开始爬取的url

    def parse(self, response):
    	next_url = response.xpath("").extract_first()  # 获取到二级URL
    	# 构造请求
    	yield scrapy.Request(next_url, callback=self.parse2)
    	# 解析函数间传递数据。parse 传递数据到parse2
    	# item = {}
    	# .. 省略item中数据的获取过程。
    	# yield scrapy.Request(next_url, callback=self.parse2, meta={"item"=item})
    	
	def parse2(self, response):
		# 对二级URL所做处理
		pass
    	

scrapy.Request 能构建一个requests,同时制定提取数据的callback函数。
scrapy.Request(url [, callback, method, headers, body, cookies, meta, dont_filter=False])

  • meta: 实现在不同的解析函数中传递数据,meta默认会携带部分信息,比如下载延迟,请求深度等。
class ItcastSpider(scrapy.Spider):
    name = 'itcast'   # 定义的爬虫名
    allowed_domains = ['itcast.cn']  # 允许爬取的域名范围
    start_urls = ['http://www.itcast.cn/channel/teacher.shtml']  # 开始爬取的url

    def parse(self, response):
    	next_url = response.xpath("").extract_first()  # 获取到二级URL
    	# 构造请求
    	# 解析函数间传递数据。parse 传递数据到parse2
    	item = {}
    	.. 省略item中数据的获取过程。
    	yield scrapy.Request(next_url, callback=self.parse2, meta={"item"=item})
    	
	def parse2(self, response):
		# 对二级URL所做处理
		item = response.meta["item"]
		pass
  • dont_filter: 让scrapy的去重不会过滤当前url,scrapy默认有url去重的功能,对需要重复请求的url有重要用途。
pipeline
class MyspiderPipeline:
	def open_spider(self, spider):
		pass
		
    def process_item(self, item, spider):
    	# 在这个方法中编写数据存储的方法。
        return item
	
	def close_spider(self, spider):
		pass

完成pipeline代码后,需要在settings中设置开启pipeline

ITEM_PIPELINES = {
   'myspider.pipelines.MyspiderPipeline': 300,
   # 'myspider.pipelines.MyspiderPipeline' pipeline的位置
   # 300 权重。当有多个pipeline时,通过权重大小,决定执行顺序。
}

从settings中的pipeline设置可以看出,pipeline可以有多个。主要为了两个目的:

  • 可能会有多个spider,不同的pipeline处理不同的item内容。
  • 一个spider的内容可能要做不同的操作,比如存入不同的数据库。

注意:

  • pipeline的权重越小,优先级越高。
  • pipeline中process_item方法名不能修改。

open_spider方法,不是默认生成的。这个方法从名字可以看出,是在开始爬虫之前调用的方法(既此方法运行在爬虫文件之前。)这个方法在整个爬虫过程中,只会执行一次。通过此方法,可以对spider对象预先添加属性信息,还可以进行连接数据库实例化,打开本地文件等操作。
closer_spider方法,在爬虫关闭的时候执行,仅执行一次。可以进行数据库关闭连接,关闭本地文件等结束操作。

items

为要提取的数据构建数据模型。
items.py

import scrapy
class MyspiderItem(scrapy.Item):
    name = scrapy.Field()
    .. 省略更多

在爬虫文件中,如果使用了items,则只能构建模型中定义的字段。如果模型中没有,会报错。
如果爬虫文件中成功使用了items获取数据,在pipeline.py文件中,item则为对应模型,可以通过下面方法进行判断。

from myspider.items import MyspiderItem
class MyspiderPipeline:
    def process_item(self, item, spider):
    	if isinstance(item, MyspiderItem):
    		..
    		# 通过dict(item) 可以强转为dict字典类型。
        return item
Log

在settings中,可以设置在终端输出日志的Log等级。通过设置这个参数,可以禁止scrapy的加载和运行信息在终端的输出。

LOG_LEVEL = "WARNING"

在可能出现异常的地方,加log输出。如爬虫文件,pipelines.py文件。

import logging
logger = logging.getLogger(__name__)  # 这样处理之后,打印出的日志可以看到所在文件名

# 可能出现异常的地方
logger.warning("loginfo")

日志不在终端打印,保存本地的设置方法。
在settings中添加

LOG_LEVEL = "WARNING"
LOG_FILE = "./log.log"
settings

settings中的配置都有详细的文档教学,可以自行在使用时进行查看。

BOT_NAME = 'myspider'

SPIDER_MODULES = ['myspider.spiders']
NEWSPIDER_MODULE = 'myspider.spiders'

LOG_LEVEL = "WARNING"
LOG_FILE = "./log.log"
# 设置user_agent。
USER_AGENT = ''

# Obey robots.txt rules 这个参数决定了爬虫是否遵循robots协议。
ROBOTSTXT_OBEY = True

# 设置最大并发请求数 (default: 16)
CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# 是否使用cookies,scrapy在请求二级url时,可以自动携带上级cookies (enabled by default)
#COOKIES_ENABLED = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# 重写headers,注意user_agent,cookies 信息不能放入请求头,需要单独处理:
#DEFAULT_REQUEST_HEADERS = {
#   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#   'Accept-Language': 'en',
#}

# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
#    'myspider.middlewares.MyspiderSpiderMiddleware': 543,
#}

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
#    'myspider.middlewares.MyspiderDownloaderMiddleware': 543,
#}

# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
#}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'myspider.pipelines.MyspiderPipeline': 300,
}

# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False

# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

Scrapy CrawlSpider 使用

通常情况下,在爬取页面中的URL时,我们是根据xpath一个个获取,然后构造。
使用CrawlSpider可以让这个过程变得简单,让scrapy框架根据我们设定的规则,自动获取所需的URL。

# 创建爬虫命令
scrapy genspider -t crawl spidername spider域名

生成的爬虫文件

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class ChufaSpider(CrawlSpider):
    name = 'chufa'
    allowed_domains = ['cicr.gov.cn']
    start_urls = ['http://cicr.gov.cn/']

    # 定义提取URL地址规则
    # Rule是一个对象,在rules中实例化
    # rules是一个元祖,可以实例化多个Rule
    rules = (
    	# allow URL的正则。不需要主域名,会自动补充。
    	# LinkExtractor 连接提取器,提取URL地址
    	# callback 提取出来的URL地址的response会交给callback处理,根据业务判断是否需要此参数。
    	# follow 当前URL地址的响应是否重新经过rules来提取URL地址
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        item = {}
        #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
        #item['name'] = response.xpath('//div[@id="name"]').get()
        #item['description'] = response.xpath('//div[@id="description"]').get()
        return item

注意:

  • CrawlSpider爬虫文件中,不能定义parse方法,parse有特殊功能。
  • 如果多个Rule都满足某一个URL,会从rules中选择第一个满足的进行操作。

LinkExtractor更多常见参数:

  • deny:满足括号中正则的URL一定不提取,优先级高于allow。
  • allow_domains:会被提取的连接的domains。
  • deny_domains:一定不会被提取连接的domains。
  • restrict_xpaths:使用xpath表达式过滤连接。

spider.Rule更多常见参数:

  • process_links:指定某个函数。在获取到连接列表时将会调用该函数,主要用来过滤URL。
  • process_request:指定某个函数。在该Rule提取到的每个requests时都会调用该函数,用来过滤requests。

Downloader Middlewares 下载中间件

当需要对请求和响应做自定义处理时,使用下载中间件。
Downloader Middlewares 默认的方法:

  • process_request(self, request, spider)
    当每个request通过下载中间件时,该方法被调用。
  • process_response(self, request,response,spider)
    当下载器完成http请求,传递响应给引擎的时候调用。
class RandomUserAgentMiddleware:

    def process_request(self, request, spider):
    	# 提前在settings中定义USER_AGENTS_LIST列表。
        ua = random.choice(spider.settings.get("USER_AGENTS_LIST"))  
        request.headers["User-Agent"] = ua
        request.meta["proxy"] = "协议+ip+端口"  # 添加代理

    def process_response(self, request, response, spider):
        print(request.headers["User-Agent"])  # 查看每次使用的ua是否不同。
        return response

中间件定义之后,需要在settings中注册,同pipeline。

Scrapy 模拟登陆

在不使用scrapy,使用requests做爬虫时,模拟登陆有三种方法:

  • 直接携带cookies请求页面。可以放到header中,也可以在requests中给到cookies参数。
  • 找接口发送POST请求,存储cookies。
  • selenium.找到对应的input标签,输入信息点击登陆。

使用scarpy模拟登陆,也是同样的三种方法:

  • 直接携带cookies。不能放到请求头中,必须给到cookie
  • 找接口发送POST请求,scrapy默认会自动存储cookies(在settings中可以设置)。
  • selenium把登录后的cookies保存到本地,scrapy发送请求前读取本地cookies。

在开始使用scrapy模拟登陆之前,先要理解start_url的访问流程。首先,上面提到过,start_url在访问时,不会被allowed_domains规则所限制(这个限制是通过OffsiteMiddleware中间件实现的)。这是因为,我们在parse中构造的请求,是使用的scrapy.Request,而start_url的请求是使用的spider.start_requests 方法。通过源码可以看到具体实现内容。
理解了start_url的请求,就可以推测出,如果start_url是需要登录才能访问的,可以通过改写spider.start_requests 方法来实现登录后访问。

import scrapy


class ItcastSpider(scrapy.Spider):
    name = ''   # 定义的爬虫名
    allowed_domains = ['']  # 允许爬取的域名范围
    start_urls = ['']  # 开始爬取的url

	def start_requests(self):
		cookies = ""
		# cookies转化为字典
		cookies = {i.split("=")[0]:i.split("=")[1] for i in cookies.split("; ")}
		yield scrapy.Request(
			self.satrt_urls[0],
			callback=self.parse,
			cookies=cookies
		)

    def parse(self, response):
    	url = ''
    	# 默认情况下,scrapy在下次url请求时,可以自动携带cookies,不需要再次指定。
    	yield scrapy.Request(
			url,
			callback=self.parse_second
		)

	def parse_second(self, response):
    	pass

小技巧:可以在settings中设置 COOKIES_DEBUG=TRUE 参数,这样我们就可以在终端输出中看到cookie的传递,和cookie信息了。

Scrapy 模拟POST请求

使用scrapy.FormRequest方法。

class ItcastSpider(scrapy.Spider):
    name = ''   # 定义的爬虫名
    allowed_domains = ['']  # 允许爬取的域名范围
    start_urls = ['']  # 开始爬取的url
    def parse(self, response):
    	post_data = {}
		.. 省略构造post_data
		yield scrapy.FormRequest(
			"url",  # post请求路径
			formdata=post_data,
			callback=self.after_login
		)

	def after_login(self, response):
    	pass

上面这种方法,需要人为找到POST请求地址。scrapy提供了自动获取Form表单提交地址路径的方法。

class ItcastSpider(scrapy.Spider):
    name = ''   # 定义的爬虫名
    allowed_domains = ['']  # 允许爬取的域名范围
    start_urls = ['']  # 开始爬取的url
    def parse(self, response):
		yield scrapy.FormRequest.from_response(
			response,  # 自动的从response中寻找Form表单提交地址
			formdata={"login":"..","password":".."},  # 需要输入表单的input名和要输入的值。scrapy自动填充后提交。
			callback=self.after_login
		)

	def after_login(self, response):
    	pass

Scrapy shell

可以在终端,对Scrapy提供的参数进行使用和测试。包括测试xpath的结果、查看response包含的信息等等。

scrapy shell <url>

Scrapy Redis

scrapy_redis在scrapy的基础上实现了更多,更强大的功能。具体体现在:request去重,爬虫持久化,轻松实现分布式爬虫。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值