python爬虫

python爬虫

基础爬虫

requests

import requests

# get请求
response = requests.get(url=url, params=params,headers=headers)

# post请求
response = requests.post(url=url, data=data_form, headers=headers)

get和post请求的数据格式都是字典,除此之外,比较重要的就是找到关键url

response返回数据

  • text:字符串
  • content:二进制
  • json:对象

持久化存储

文件夹操作
import os
if not os.path.exists('./test'):
	os.mkdir('./test')

如果

普通存储
with open("../file/baidu.html", 'w', encoding='utf-8') as fp:
	fp.write(response.text)
json格式(json对象存储)
dic_obj = response.json()
fp = open('./1.json','w',encoding='utf-8')
json.dump(dic_obj,fp=fp,ensure_ascii=False)

数据解析

  1. 标签定位
  2. 对标签或标签对应的属性中存储的数据值进行提取

正则re

  • re.findall(pattern,string,flag)

    import re
    
    a = re.findall(ex,str,re.S)
    

    ex为正则表达式,str为给定的字符串,re.S表明re会将这个字符串作为一个整体

  • re.sub(pattern,repl,string)

    pattern:正则表达式

    repl:要替换的字符串

    string:待处理字符串

    # 将string中处于filter的字符串全部剔除
    filter=[' ','\n']
    re.sub('|'.join(filter),'',string)
    
特殊re字符
字符含义
.匹配除了换行符以外的任意字符
^匹配以某字符串开头的字符串,放在字符串前
$匹配以某字符结尾的字符串,放在字符串以后
*匹配前面的子模式零次或多次(greedy)。要匹配 * 字符,请使用 \*。
+匹配前面的子模式1次或多次(greedy)
?匹配前面的子模式零次或1次(greedy)
*?,+?,??非greedy版本的*、+、?
{m,n}匹配前面一个模式串m到n次(greedy)
{m,n}?匹配前面一个模式串m到n次(non-greedy)
\\转义特殊字符或发出特殊序列信号
[]匹配字符集合
|or,A|B,匹配模式串A或模式串B
()匹配括号以内的部分,在括号内包含模式串
特殊序列
序列含义
\b匹配空字符串,但仅在单词的开头或结尾。
\B匹配空字符串,但不在单词的开头或结尾。
\d匹配任意十进制数字,等价于[0-9]
\D匹配任意非十进制数字,等价于[^\d]
\s匹配任意空白字符
\S匹配任意非空白字符
\w匹配任意字母数字字符
\W匹配任意非字母或数字字符
\\匹配反斜杠
flags
flags含义
A使\w, \w, \b, \b, \d, \d匹配相应的ASCII字符类别
I执行不区分大小写的匹配。
M"^“匹配行首(换行符之后)以及字符串。”$"匹配行尾(换行符之前)以及字符串的结尾。
S"."匹配任何字符,不再排除换行符。
X忽略空格和注释,以获得更好看的正则。

使用的时候为函数的flag参数指定re.S即可

xpath

最常用且最高效便捷的一种解析方式,通用性最强。

  1. 实例化一个etree对象,且需要将被解析的页面源码数据加载到该对象中
  2. 调用etree对象中的xpath方法结合xpath表达式实现标签的定位和内容的捕获
实例化etree对象
from lxml import etree
tree = etree.parse(filePath)#加载html文档源码
tree.HTML('page_text')#将数据加载到该对象中
tree.xpath('xpath表达式')#解析
xpath表达式
语法含义示例
/从当前标签位置开始定位标签/html/div
//从任意位置定位标签//div
[@]属性定位//div[@class=“song”],定位到class为song的div
[]索引定位//div/p[3],定位第三个p标签(索引从1开始)
/text()获取标签的直系文本//div/p[3]/text(),定位第三个p标签并取出其直系文本
//text()获取标签的所有文本//div//text(),取出所有div标签中的所有文本内容
/@attrName获取属性值//img/@src,取出所有img标签的src属性值
./当前标签./@method,取出当前标签的method属性值

反爬绕过

伪装绕过

  • UA(User-Agent)伪装

    安装fake_useragent包:

    pip install fake_useragent
    

    然后:

    from fake_useragent import UserAgent
    ua = UserAgent()
    user_agent = ua.random
    
  • cookie伪装

  • Referer伪装

验证码绕过

  • 第三方验证码识别api,云打码识别,收费较低

模拟登陆

在使用requests进行模拟登陆的时候可能需要使用requests.Session才可以

session = requests.Session()
response = session.get(url=url, headers=headers)

在登录过后想要获取其他页面的数据需要携带cookie,该cookie是登陆后返回的set-cookie的值,如果使用session,则session会自动存储cookie,下次请求就没有必要携带cookie

控制请求间隔

对于只有一个线程或一个进程的程序,使用time.sleep来控制间隔

代理

绕过ip封禁,在requests中指定proxies参数值,这是一个字典

代理池的使用暂时遇到两种格式:

协程(aiohttp.ClientSession.get)、scrapy格式:

proxies = ["http://61.216.185.88:60808", "http://121.13.252.58:41564","http://113.124.86.24:9999","http://27.42.168.46:55481","http://117.114.149.66:55443"]

requests格式:

proxies = [{"http": "61.216.185.88:60808"}, {"http": "121.13.252.58:41564"},{"http": "113.124.86.24:9999"},{"http": "27.42.168.46:55481"},{"http": "117.114.149.66:55443"}]

快代理代理爬取函数:

def get_proxy():
    ip_list = []
    port_list = []
    anonymity_level_list = []
    type_list = []
    location_list = []
    response_speed_list = []

    page_nums = 15
    for page_num in range(1, page_nums):
        url = "https://www.kuaidaili.com/free/inha/" + str(page_num) + '/'
        headers = {
            'User-Agent': user_agents_pool[page_num % len(user_agents_pool)]
        }
        # response = requests.get(url=url, headers=headers, proxies=proxies[page_num % 5])
        response = requests.get(url=url, headers=headers)
        print(response.status_code)
        page_text = etree.HTML(response.text)
        ip_s = page_text.xpath("//td[@data-title='IP']/text()")
        port_s = page_text.xpath("//td[@data-title='PORT']/text()")
        anonymity_level_s = page_text.xpath("//td[@data-title='匿名度']/text()")
        type_s = page_text.xpath("//td[@data-title='类型']/text()")
        location_s = page_text.xpath("//td[@data-title='位置']/text()")
        response_speed_s = page_text.xpath("//td[@data-title='响应速度']/text()")
        ip_list.extend(ip_s)
        port_list.extend(port_s)
        anonymity_level_list.extend(anonymity_level_s)
        type_list.extend(type_s)
        location_list.extend(location_s)
        response_speed_list.extend(response_speed_s)
        time.sleep(2)
    dic = {
        'ip': ip_list,
        'port': port_list,
        'anonymity level': anonymity_level_list,
        'type': type_list,
        'location': location_list,
        'response_speed': response_speed_list
    }
    df = pd.DataFrame(dic)
    # 去除ip列和port列都重复的行
    df.drop_duplicates(df[df.duplicated('ip')&df.duplicated('port')], inplace=True)
    df.to_csv(path_or_buf='./proxy.csv', encoding='gbk', index=False)

检测到非法调试

卡死在debbuge处

  • 让开发者工具界面作为独立窗口存在

  • hook代码注入绕过,注意,刷新页面后就需要重新注入。在源代码添加新片段新片段为以下代码,之后继续执行即可

    var AAA=Function.prototype.constructor
    
    Function.prototype.constructor=function(x){
     if(X!="debugger"){ 
     return AAA(x)
     };
     return function(){};
    }
    

异步爬虫

在爬虫中使用异步实现高性能的数据爬取操作

异步爬虫方式:

  • 多进程或多线程:阻塞操作可以异步执行,但进程和线程不可无限制创建

  • 线程池或进程池:可以降低系统对进程或线程创建和销毁的一个频率,从而很好地降低系统的开销,但池中的数量仍有上限

    import time
    from multiprocessing.dummy import Pool
    
    # 进程池和线程池
    def get_page():
        return
    
    
    url_list=[]
    start_time = time.time()
    
    
    #实例化一个拥有4个线程的线程池
    pool = Pool(4)
    # 将列表中每一个列表元素传递给函数进行处理并返回一个列表
    result_list = pool.map(get_page, url_list)
    
    pool.close()
    pool.join()#主线程等待子线程运行结束后再结束运行
    

    线程池处理的是阻塞且耗时的操作

  • 单线程+异步协程

    • event_loop,事件循环,将函数注册到该事件循环,当满足某些条件,函数就会被执行

    • coroutine,协程对象

    • task,任务,是协程对象的进一步封装,包含了任务的各个状态

      # 创建task的第一种方式
      task = asyncio.create_task(...)
      
      #创建task的第二种方式
      task = asyncio.ensure_future(...)
      
    • future:代表将来执行或还没执行的任务,实际上和task没有本质区别

    • async:定义一个协程

    • await:用来将可等待的对象挂起(协程对象、Future、Task),等待对象的值得到结果后再继续向下走

    对于主线程是loop=get_event_loop(). 对于其他线程需要首先loop=new_event_loop(),然后set_event_loop(loop)
    new_event_loop()是创建一个event loop对象,而set_event_loop(eventloop对象)是将event loop对象指定为当前协程的event loop,一个协程内只允许运行一个event loop,不要一个协程有两个event loop交替运行。

协程不是由计算机提供,而是人为创造的,是一种用户态内的上下文切换技术。简而言之,就是通过一个线程实现代码块互相切换

在一个线程中如果遇到IO等待时间,线程不会一直等着,而是利用空闲的时间去干别的事。(IO多路复用)

实现协程:

  • greenlet:早期模块
  • yield关键字
  • asyncio装饰器
  • async、await关键字

多任务异步协程(asyncio)

import asyncio
import time
async def request(url):
    print(url)
    # 在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步
    # 使用time.sleep就无法实现异步
    # 对阻塞操作手动挂起
    await asyncio.sleep(2)

urls = {
    "www.baidu.com",
    "www.163.com",
    "www.souhu.com"
}

# 任务列表:存放多个任务对象
tasks = []

# 生成事件循环
loop = asyncio.new_event_loop()
for url in urls:
    c = request(url=url)
    task = asyncio.ensure_future(c, loop=loop)
    tasks.append(task)

# 使用wait封装task列表,然后一次性往事件循环中注册任务列表中的任务
results =  loop.run_until_complete(asyncio.wait(tasks))

在使用requests进行爬取时,requests.get基于同步模块,必须使用基于异步请求的模块aiohttp

async def get_page(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            # text返回文本,read返回二进制,json返回json对象
            page_text = await response.text()# 使用await进行挂起
            with open("../file/"+url[7:]+".html", 'w', encoding='utf-8') as fp:
                fp.write(page_text)

uvloop

是asyncio的事件循环的替代方案,事件循环>默认asyncio的事件循环

pip install uvloop
import uvloop

asyncio.set_event_loop_policy(uvloop.EvventLoopPolicy())

其他的处理与asyncio的事件循环处理的方式一致

爬虫框架

selenium

  • 便捷地获取网站中动态加载的数据
  • 便捷实现模拟登陆

基于浏览器的自动化模块

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver import ActionChains

from lxml import etree
from time import sleep

options = webdriver.ChromeOptions()

# 设置无头模式,浏览器无可视化界面运行
# options.headless = True

# options.add_argument('lang=zh_CN.UTF-8')

options.add_argument('lang=zh_CN')

# 设置加载策略,默认为normal,很慢
options.page_load_strategy = 'eager'

# 将驱动程序放在python目录下可以不用指定executable_path路径
# browser = webdriver.Chrome(options=Chrome_options)
browser = webdriver.Chrome()

browser.get(url = "https://cn.bing.com/")

page_text = browser.page_source

# print(page_text)

# 搜索框定位
search_input = browser.find_element(value="sb_form_q")

search_input.send_keys("你好")

# 提交按钮定位
go_input = browser.find_element(value="sb_form_go")

go_input.submit()

# 执行js代码,将页面滚动到最底部
browser.execute_script(script="window.scrollTo(0, document.body.scrollHeight)")

# 回退
browser.back()

#前进
browser.forward()

sleep(10)

# 关闭浏览器
browser.quit()
标签定位
search_input = browser.find_element(value="sb_form_q")

默认是通过id定位标签

iframe
browser.switch_to.frame("待切换的iframe的id")# 切换标签定位作用域

查看一个页面的第一层iframe有多少(在控制台执行)

frames.length()

通过索引可以获取对应的iframe

frames[i]

查看第i个iframe的子iframe个数

frames[i].frames.length()
动作链
from selenium.webdriver import ActionChains

action = ActionChain(browser)

action.click_and_hold(标签变量)#点击并抓住这个标签

action.move_by__offset(x,y).perfome()#拖动到某个位置并立即执行

action.release()#释放动作链
规避检测
# 规避检测
options.add_experimental_option('excludeSwitches', ['enable-automation'])

# 禁用浏览器自动控制
options.add_argument("--disable-blink-features=AutomationControlled")
无头浏览器
# 无头浏览器
options.add_argument("--headless")
options.add_argument("--disable-gpu")

scrapy

集成了很多功能,并且具有很强通用性的项目模板,其功能为:

  • 高性能的持久化存储
  • 异步数据下载
  • 高性能的数据解析
  • 分布式
scrapy原理
scrapy五大核心组件
  • 调度器:过滤器和队列,过滤完成的请求放入队列

  • 管道

  • 引擎

  • 下载器

  • spider:发送请求,数据解析
    请添加图片描述

中间件
  • 下载中间件:处于引擎与下载器之间,批量拦截到整个工程中所有的请求和响应

    • 拦截请求

      可以做UA伪装,设置代理ip

    • 拦截响应

      可以篡改响应数据,响应对象

  • 爬虫中间件:处于spider与引擎之间

创建工程

在终端输入scrapy startproject project_name

目录结构:

└─scrapy_test
    │  items.py
    │  middlewares.py
    │  pipelines.py
    │  settings.py
    │  __init__.py
    │
    └─spiders 
            __init__.py
  • spiders:爬虫文件夹,在该文件夹下必须创建一个爬虫文件

    scrapy genspider spiderName www.xxx.com,如果是基于CrawlSpider则按以下方式创建爬虫文件:

    scrapy genaspider -t crawl xxx www.xxx.com

  • settings.py:存放工程配置

  • items.py:数据的封装,使用scrapy.field

  • pipelines.py:专门用来处理item

工程执行

scrapy crawl spliderName就会执行指定的爬虫文件

  • --nolog:不打印日志

如果需要在某个python文件中对spider进行调用,那么可以采用以下方式:

  1. cmd方式(cmdline.execute或者os.system):该方式需要切换工作目录,比较麻烦

  2. CrawlerProcess方式,相比cmd来说更方便,而且可以看日志

    from crawler.crawler.spiders.crawlAll import CrawlallSpider
    from scrapy.crawler import CrawlerProcess
    from scrapy.utils.project import get_project_settings
    
    if __name__ == '__main__':
        process = CrawlerProcess(get_project_settings())
        process.crawl(CrawlallSpider)
        process.start()
    

    CrawlallSpider是自定义的爬虫类,其文件路径为/crawler/crawler/spider/crawlAll

  3. CrawlerRunner方式,看不到日志

    from scrapy.crawler import CrawlerRunner
    from scrapy.utils.project import get_project_settings
    from twisted.internet import reactor
    from crawler.crawler.spiders.crawlAll import CrawlallSpider
    
    runner = CrawlerRunner(get_project_settings())
    
    # [runner.crawl(spider) for spider in spiders]
    runner.crawl(CrawlallSpider)
    d = runner.join()
    d.addBoth(lambda _: reactor.stop())
    reactor.run()
    

注意,在使用第二种或第三种方式时,在setting文件中定义的管道等配置可能不会被读取,最好放在爬虫文件的custom_settings中或者如此指定

process = CrawlerProcess(settings={
            'ITEM_PIPELINES': {
                'crawl.crawler.crawler.pipelines.CrawlerRedisPipeline': 300
            },
            'REQUEST_FINGERPRINTER_IMPLEMENTATION': '2.7'
        })
spiders

指的是使用genspider创建的处于spiders目录下的文件

  • name:爬虫源文件名

  • allow_domains:限定哪些域名下的url可以别允许发送请求,但一般不用

  • start_urls:在该列表中存放的url会被自动发送请求

  • parse函数:用于数据解析

    • response.xpath:与etree的xpath有一些不同,spider经过xpath后的结果是一个Selector列表,通过extract函数可以取出每个selectordata参数存放的值
  • start_requests函数:最开始发起请求的地方

setting配置文件
# 是否遵循robots.txt
ROBOTSTXT_OBEY = False


# 只显示error等级的日志信息
LOG_LEVEL = 'ERROR'

# 指定代理
USER_AGENT = ''

# 开启管道
ITEM_PIPELINES = {
    'xxxxx.pipelines.xxxxxPipeline':300,#xxxxx代指工程名,300表示优先级,数值越小优先级越高
}

# 开启爬虫中间件
SPIDER_MIDDLEWARES = {
 'xxxxx.middlewares.xxxxxSpiderMiddleware': 543,
}

# 开启下载中间件
DOWNLOADER_MIDDLEWARES = { 'xxxxx.middlewares.xxxxxDownloaderMiddleware': 543,
}

# 在高延迟情况下设置的最大下载延迟
AUTOTHROTTLE_MAX_DELAY = 60

# 配置Scrapy的最大并发请求 (default: 16)
CONCURRENT_REQUESTS = 32

# 指定一网站的请求延迟
DOWNLOAD_DELAY = 3

# 禁用 cookies (enabled by default)
COOKIES_ENABLED = False

# 指定基于ImagesPipeline管道的存储目录路径
IMAGES_STORE = 'path'

在执行工程时,通过指定-s参数可以设置临时配置,比如

scrapy crawl spiderName -s DOWNLOAD_DELAY=10

在自定义的spider类中添加一个变量,如下:

class ImgSpiderSpider(scrapy.Spider):
    name = 'img_spider'
    allowed_domains = ['sc.chinaz.com']
    start_urls = ['https://sc.chinaz.com/tupian/xingkongtupian.html']
    custom_settings = {
        'DOWNLOAD_DELAY': 5
    }

    def parse(self, response):
        print(self.crawler.settings.get('DOWNLOAD_DELAY'))

然后执行工程可以得到DOWNLOAD_DELAY的值8即为custom_settings中定义的值。

持久化存储
基于终端

parse方法的返回值存储于本地文件中

scrapy crawl spider_name -o filepath

存储的文件格式有限制

基于管道
  • 在爬虫文件的parse中进行数据解析

  • items.py中定义相关的属性

  • 将解析的数据封装存储到item类型的对象里,使用scrapy.field进行对象的定义

    obj1 = scrapy.field()
    obj2 = scrapy.field()
    
  • items.pyimportitems类并进行实例化,然后进行将解析的数据存入item对象中,最后使用yielditem传递给管道

    from items import xxxxItem
    
    def parse(self, response):
        .....
        .....
        .....
    	item = xxxxItem()#实例化
    	item[obj_name] = data
        yield item
    
  • pipelines.pyprocess_item方法中接受爬虫文件提交过来的item对象存储的数据并进行持久化存储操作,注意需要重写父类方法

    # 对父类的方法进行重写,该方法只在开始爬虫的时候被调用一次
    fp = None
    def open_spider(self,spider):
    	self.fp = open(...)
    	
    	
    def close_spider(self, spider):
    	self.fp.close()
    
  • 在配置文件中开启管道

数据库持久化存储

可以自己在pipelines.py中新写一个管道类,然后在其中重写方法

class sqlitePipeLine(object)
	conn = None
    cursor = None
	def open_spider(self, spider):
		self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',charset='utf8')
	def process_item(self,spider):
		self.cursor = self.conn.cursor()
        try:
        	self.cursor.execute('sql语句')
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()
        return item
    def close_spider(self, spider)
    	self.cursor.close()
        self.conn.close()

最后别忘记还要在setting文件中添加该管道。另外,只有在process_item方法中return item,其他pipelie才可以拿到item并对其中的数据做处理

请求传参

使用场景:深度爬取,需要发出新的请求

yield scrapy.Request(url=new_url, callback=self.xxx_parse)

自定义解析函数后可以将其作为回调函数

def xxx_parse(self, response):
	.....

请求传参指的是将当前parse中的变量传递给其他parse函数,可以在Request中指定meta参数

yield scrapy.Request(url=new_url, callback=self.xxx_parse, meta={'item':item})

然后在xxx_parse解析函数里面按照字典的方式取数据:

item = response.meta['item']

另外,Request既可用于get请求,又可用于post请求(指定method参数),进行post请求发送,还可以通过FormReques

ImagesPipeline(图片数据爬取)

专门用于图片数据爬取,只需要将图片的url发送给该管道,管道就可以对图片的src进行请求发送获取图片的二进制数据。

继承ImagesPipeline后重写以下方法:

from scrapy.pipelines.images import

class MyInagesPipeline(ImagesPipeline):
    # 提供url自动下载图片
    def get_media_requests(self, item, info):
    	yield scrapy.Request(item['url'])
    
    # 指定图片存储名
    def file_path(self, request, response=None, info=None, *, item=None):
        img_name = request.url.split('/')[-1]
		return img_name
    #将item传递给下一个即将执行的管道类
    def item_completed(self, results, item, info):
        return item

图片存储的目录需要通过setting来指定,具体查看setting配置文件。

middlewares中间件

中间件的使用需要在setting文件中开启

下载中间件(DownloadMiddlewares)

scrapy启用下载中间件时会结合DOWNLOADER_MIDDLEWARES_BASEDOWNLOADER_MIDDLEWARES,自带的DOWNLOADER_MIDDLEWARES_BASE不可覆盖,如果要覆盖,则需要在自定义的DOWNLOADER_MIDDLEWARES中将对应的下载中间件置为None,以下为相应的下载中间件:

DOWNLOADER_MIDDLEWARES_BASE = 
{
    'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
    'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
    'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
    'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
    'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
    'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
    'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
    'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
    'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
    'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
}

自定义下载中间件:

class xxxDownloadMiddlewares(object):
    user_agent_pool=[xxxxxxx]
    proxy_http = [xxxxx]
    proxy_https = [xxxxx]
	# 拦截正常请求
	def process_request(self, request, spider):
        # UA伪装
        request.headers['User-Agent'] = random.choice(self.user_agent_pool)
        return None
    
	# 拦截所有的响应
	def process_response(self, request, response, spider):
		return response
	
	# 拦截发生异常的请求
	def process exception(self, request, exception, spider):
        if request.url.split(':')[0]=='http':
            # 代理ip 'proxy'是request固定携带的
            request.meta[ 'proxy'] = random.choice(proxy_http)
        else
        	request.meta[ 'proxy'] = random.choice(proxy_https)
		return request
  • process_request

    return说明
    NoneRequest对象会被递交给下一个下载中间件
    Response返回Response对象后会交由process_response处理
    RequestScrapy将停止调用process_request()方法并重新调度返回的请求
    raise IgnoreRequest不处理该请求
  • process_response

    return说明
    ResponseResponse对象会被递交给下一个下载中间件
    Request中间件链将停止,返回的请求将重新安排在将来发送。这与从process_request()返回请求的行为相同。
    raise IgnoreRequest不处理该响应
重定向中间件

scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware设置一个优先级,然后在setting文件中启用它:

'REDIRECT_ENABLED': 'True',  # 启用重定向中间件

然后scrapy就可以自动重定向

cookie中间件

scrapy.downloadermiddlewares.cookies.CookiesMiddleware设置一个优先级,然后在setting文件中启用它:

COOKIES_ENABLED=True# 自动添加cookie

这会使得爬取到的cookie会自动附加到后续的request中

如果想要在debug看cookie的信息,则可以再添加一个配置:

COOKIES_DEBUG=True
重试中间件

scrapy.downloadermiddlewares.retry.RetryMiddleware设置一个优先级,然后在setting文件中启用它:

RETRY_ENABLE=True

然后scrapy就可以再次处理原来失败的请求。

还可以设置retry的次数和碰到哪些http code就retry

RETRY_TIMES=5
RETRY_HTTP_CODES=[302]
CrawSpider全站数据爬取

spider的子类。

全站数据爬取的方式:

  • 基于spider:手动请求
  • 基于CrawSpider
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class AllspiderSpider(CrawlSpider):
    name = 'allSpider'
    allowed_domains = ['www.xxx.com']
    start_urls = ['http://www.xxx.com/']

    rules = (
        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

  • 链接提取器(LinkExtractor):根据指定规则进行指定链接的提取,即便有重复的链接也会自动过滤

    • allow:一个用来提取链接的正则表达式,为空字符串时提取页面中的所有链接
  • 规则解析器(Rule)

    • link:指定链接提取器
    • callback:指定回调函数
    • follow:指定是否可以自动更换start_url,其实就是将链接提取器继续作用到链接提取器提取到的链接所对应的页面中。
  • parse_item

    对链接提取器中的链接得到的response进行解析

crawlspider的url均获取自response,重定向的response不会流向parse_item,需要自行在下载中间件中处理,比如访问一个页面302重定向到登录页面,这个时候你是获取不到登录页面数据的,只能在下载中间件中的process_response中获取

链接提取器
from scrapy.linkextractors import LinkExtractor

协程引用

在setting配置文件中添加:

TWISTED_REACTOR = 'twisted.internet.asyncioreactor.AsyncioSelectorReactor'
xpath选择器(response.xpath)

使用xpath选择器会返回一个selector对象,该对象格式为:

[<Selector xpath='xpath expression' data='Example website'>]
方法说明
get提取单个文本数据
getall提取所有文本数据,返回列表

以下是示例,其中response就是parse函数中的response:

def parse_item(self, response):
    form_list = response.xpath("//form")
    print(response.status, ':', response.request.method, ':', response.url)
    for form in form_list:
        method = form.xpath('./@method').get()
        action = form.xpath('./@action').get().strip()
        if action == "":
            url = response.url
        elif str(action).startswith("http"):
            url = action
        else:
            url = response.url[:(response.url).rindex('/') + 1] + action
        input_list = form.xpath('.//input')
        params = {}
        for _input in input_list:
            params[_input.xpath('./@name').get()] = '1'

        yield scrapy.FormRequest(url=url, method=method, formdata=params, callback=self.parse_item)

form_list是一个selector对象列表,迭代其中的每个form表单对象,并以当前表单对象为基准提取其对象的method和action属性,又以单签表单对象为基础,获取其下的input对象,然后与前面类似,最后每个表单都会再次发出一次请求,并且其响应依旧由当前函数解析。

分布式爬虫

搭建分布式机群,让其对一组资源进行分部联合爬取,可以提高爬取的效率

实现分布式需要安装scrapy-redis,原生的scrapy不能实现分布式爬虫

  • 创建工程

  • 创建基于CrawlSpider的爬虫

  • 修改爬虫文件

    from scrapy_redis.spiders import RedisCrawlSpider
    
    class LocalSpider(RedisCrawlSpider):
        name = '58'
    
        # 注释以下两行变量
        # allowed_domains = ['cq.58.com']
        # start_urls = ['http://cq.58.com/']
        redis_key = '58' # 被共享的调度器队列的名称
        rules = (
            Rule(LinkExtractor(allow=r'shouji/pn\d+'), callback='parse_item', follow=True),
        )
    
        def parse_item(self, response):
            item = {}
            title_list = response.xpath("//td[@class='t'/a[1]//text()").extract()
            location_list = response.xpath("//td[@class='t'/p[1]//text()").extract()
            price_list = response.xpath("//td[@class='t'/p[3]//text()").extract()
            print(title_list)
            for key, title in enumerate(title_list):
                item = ScrapyRedisTestItem()
                item['title'] = title
                item['location'] = location_list[key]
                item['price'] = price_list[key]
    
                yield item
    
    
    
    
  • 修改setting配置文件

    • 指定可以被共享的管道

      ITEM_PIPELINES = {
      'scrapy_redis.pipelines.RedisPipeline':400
      }
      
    • 指定调度器

      # 增加一个去重容器类的配置,使用redis的set集合来存储请求的指纹数据,从而实现请求去重的持久化
      DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
      #使用scrapy-redis自己调度器,不使用scrapy默认的调度器
      SCHEDULER = "scrapy_redis.scheduler.Scheduler"
      #配置调度器是否要持久化,不调度状态持久化,不清理redis缓存,允许暂停/启动爬虫
      SCHEDULER_PERSIST = True
      
  • redis相关配置

    redis的配置文件(.conf文件):

    # 关闭保护模式,允许写数据
    protected-mode no
    
    # 注释以下语句
    # bind 127.0.0.1
    
    • 启动redis服务

      redis-server 配置文件
      
    • 启动客户端(其他要使用redis的分布式主机)

      redis-cli
      
    • 执行工程

      scrapy runspider xxx.py
      
    • 启动客户端后指定调度器队列名并向其中放入起始url

      lpush 调度器队列名 起始url
      
    • 在setting文件中指定提供redis服务的主机和端口

      REDIS_HOST = '127.0.0.1'
      REDIS_PORT = 6379
      

除了使用默认的redispipeline,也可以自定义redispipeline:

class CrawlerRedisPipeline(RedisPipeline):
    r = None

    def open_spider(self, spider):
        self.r=redis.Redis(host=redis_host, port=redis_port, db=0, decode_responses=False
)

    def process_item(self, item, spider):
        response = item['response']
        ser_response = pickle.dumps(response)
        self.r.push(redis_response_1_key, ser_response)
        return item

    def close_spider(self, spider):
        self.r.close()

decode_responses=False可以绕过一些编码问题

增量式爬虫

用于检测网站数据更新的情况,只会爬取网站最新更新出来的数据,这意味着需要对上一次爬取得到的链接进行存储(可以存到redis的set集合中)

scrapy-redis在进行爬取后会自动去重,也就是说遇到重复的链接将不再存储

报错修改

编码为utf-8 response仍为乱码

一般来说,response返回的内容如果为乱码,那么基本就是没有设置正确的编码,这可以使用以下代码解决

response.text.encode('utf8')

但是还有一个情况,当在header中设置了以下参数也会出现乱码

"Accept-Encoding":"gzip, deflate, br"

accept-encoding表示你发送请求时告诉服务器,我可以解压这些格式的数据,因此爬取的response被压缩成了这样的格式。requests不支持br,所以一旦Accept-Encoding包含br且服务器返回br格式的数据,那么就会出现乱码,故删除br即可解决问题。

"Accept-Encoding":"gzip, deflate"

pip安装包时报错 ERROR: Failed building wheel for xxx

去以下网站下载对应的whl包并使用pip安装

Archived: Python Extension Packages for Windows - Christoph Gohlke (uci.edu)

Scrapy 中 ImagesPipeline 无法执行

pip install pillow

启用下载中间件后卡死在telnet的监听处

检查process_request函数返回值是不是None

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值