前记:爬虫框架部分整理完成,后续慢慢完善,
1)仅作为个人学习,如有冒犯,告知速删!
2)不想误导,如有错误,不吝指教!
创建工程:
-
scrapy startproject name
-
cd proName
-
scrapy genspider spiderName urlName(限制爬虫的爬取范围)
-
执行:scrapy crawl spiderName
-
-
start_urls = []=====>列表中的元素会被scrapy自动进行发送--几个url,请求
setting必备设置:
-
robotstxt_obey = False
-
USER_AGENT = ' '
-
LOG_LEVEL = 'ERROR'
-
LOG_FILE = 'log.txt'----一般不用
scrapy框架中:
-
xpath返回的列表中的列表元素是selector对象,我们需要解析获取的字符串的数据
-
必须经过一个extract() 操作才可以将该对象汇总存储的字符串的数据获取
.extract()----列表
.extract_first()-----字符串
---问题:xpath返回的列表中的列表元素有多个(selector对象),想要将每个列表元素对应的selector中的字符串取出--------使用 extract()
scrapy数据解析
-
xpath语法
-
通过返回的response数据可以直接直接进行Xpath解析。
-
scrapy持久化存储
-
基于终端指令:
-
只可以将parse方法的返回值存储到电脑磁盘中
-
scrapy crwal first -o file.csv-------->将当前返回值存储到file文件中csv\json
-
-
-
基于管道:>pipelines.py
-
编码流程
-
-
数据解析(在爬虫类中)
-
在item的类中定义相关的属性(爬虫类)
-
将解析的数据存储封装到item类型的对象中;
-
将item对象提交给管道 yield
-
在管道类中process_item方法负责接收item对象,然后对item进行任意形式的格式持久化存储
-
在settings中设置MySQL的配置信息
# 如下所示
# mysql 配置
MYSQL_DB_NAME = 'scrapy_db'
MYSQL_HOST = '127.0.0.1'
MYSQL_USER = 'root'
MYSQL_PASSWORD = '123456'
-
-
-
-
细节补充:
-
管道文件中的一个管道类表示将数据存储到某一个形式的平台中。
-
如果管道文件中定义多个管道类,爬虫类提交的item的操作会给到优先级最高的管道类,只有优先级最高的管道类才可以接受到item,剩下的管道类是需要从优先级最高的管道中接受item;
-
process_item 方法的实现中的return item的操作表示item传递给下一个即将被执行的管道类
-
手动请求发送:
yield scrapy.Request(url,callback)-----发送的get请求
对yield总结:
1). 向管道提交item的时候:yield item
2). 手动请求发送:yeld scrapy.Request(url,callback)
手动发送post请求:
yield scrapy.FormRequest(url,formdata,callback):formdata是一个字典表示请求参数
scrapy五大核心组件:
- 略(网上都有)
scrapy的请求传参:
- 作用:实现深度爬取
- 使用场景:使用scrapy爬取的数据没有存在同一张页面的数据
- 传递item:使用meta的技巧----yield scrapy.Request(url,callback,meta)
- 接受item:response.meta
提升scrapy爬取数据的效率(配置文件中):
- 增加并发:
CONCURRENT_REQUESTS = 100
- 降低日志级别:
LOG_LEVEL='ERROR'
- 禁止cookies:
COOKIES_ENABLE = True
-禁止重试:
retry_ebable = False
- 减少下载超时:
DOWNLOAD_TIMEOUT = 3----请求超过3,丢弃,
scrapy的中间件(middlewares):
--概念:下载中间件
--->处于引擎和下载器
--封装了两个类:
NameDownloaderMiddleware;NameSpiderMiddleware
--一次请求经过两次中间件:
---必须经过引擎:
-
首先由调度器(scheduler)到下载器(downloader)之间可以进行request的修改与设置;
-
再由下载器(downloader)到主爬虫(spider)之间可以进行response的修改与设置;
-- 批量拦截所有的请求响应
-- 拦截请求
-- 篡改请求的头信息(UA伪装)
-- 篡改请求对应的ip(代理)
-- 拦截响应:
-- 篡改响应数据,篡改响应对象
下载器中间件中的核心方法(NameDownloaderMiddleware):
-
位置:处于scrapy的Request和Response之间的处理模块;
-
process_request---拦截正常请求,并修改与设置
-
进行ua伪装:resquest.headers["User-Agent"] = "xxx "
-
-
1 #middlewares中间件重写,记得开启该中间件 2 from scrapy import signals 3 import random 4 from xbhog.settings import USER_AGENTS_LIST 5 6 class UserAgentMiddleware(object): 7 8 def process_request(self,request,spider): 9 #设置随机请求头 10 ua = random.choice(USER_AGENTS_LIST) 11 #设置初始URL中的UA 12 request.headers['User-Agent'] = ua
-
1 #settings设置 2 USER_AGENTS_LIST = [ 3 "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", 4 "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", 5 "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", 6 "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)", 7 "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", 8 "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", 9 "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", 10 "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5" 11 ]
-
-
有一个fake-useragent,自行百度下用法;
-
-
-
进行代理设置:resquest.meta["proxy"] = proxy
-
-
代理ip的webapi发送请求获取一个代理ip
-
添加代理:基本认证与摘要认证
-
source:一文读懂HTTP Basic身份认证
-
1 #settings设置 PROXY_LIST = {"ip_port":"ip:port","user_pass":"user:pass"}
-
1 #付费代理 2 import base64 3 4 # 代理隧道验证信息 这个是在那个网站上申请的 5 proxyServer = 'http://proxy.abuyun.com:9010' # 收费的代理ip服务器地址,这里是abuyun 6 proxyUser = 用户名 7 proxyPass = 密码 8 #Basic后面有一个空格 9 proxyAuth = "Basic " + base64.b64encode(proxyUser + ":" + proxyPass) 10 11 class ProxyMiddleware(object): 12 def process_request(self, request, spider): 13 # 设置代理 14 request.meta["proxy"] = proxyServer 15 # 设置认证 16 request.headers["Proxy-Authorization"] = proxyAuth
-
-
返回None值:没有return也是返回None,该request对象传递给下载器,或通过引擎传递给其他权重低的process_request方法
-
返回Response对象:不再请求,把response返回给引擎j交给spider解析
-
-
-
-
-
-
1 #判断关键字是否在url中 2 if "xxxxx" in resquest.url: 3 driver = webdriver.Chorme() 4 driver.get(url) 5 time.sleep(3) 6 7 data = driver.page_source 8 9 driver.close() 10 #创建相应对象 11 res = HtmlResponse(url=url,body=data,encoding='utf-8',request=request)
-
1 from scrapy.http import HtmlResponse 2 import time 3 4 5 #设置selenium的中间件 6 class SelemiumSpiderMiddleware(object): 7 8 def process_request(self, request, spider): 9 spider.driver.get(request.url) 10 time.sleep(1) 11 #获得渲染后的网页源代码 12 page_text = spider.driver.page_source 13 spider.driver.close() 14 #创建响应对象 15 return HtmlResponse(url=request.url, body=page_text, request=request, encoding='utf-8')
-
-
-
process_response--拦截所有的响应
-
在downloader执行Request下载后,会得到对应的response,在发送到spider之前,可以使用process_response来对数据进行处理。
-
-
process_exception--拦截发生异常的请求对象
-
当downloader或者process_request()方法抛出异常是,该方法会被调用;
-
spider中间件:(NameSpiderMiddleware):
-
spider Middleware是接入到scrapy的Spider处理机制的钩子框架;
-
在Downloader生成Response之后,Response会被发送到Spider之前,首先经过SpiderMiddleware处理,当spider处理生成item和Request之后,还会经过SpiderMiddleware处理。
-
暂时没有用到,只大体学习记录;
--四大核心方法(百度自行了解):
-process_spider_output
-process_spider_exception
-process_start_requests
CrawlSpider:
- 基于scrapy进行全站数据爬取的一种新的手段
-
CrawlSpider就是spider的一个子类
-
链接提取器(LinkExtractor):
-
规则解析器(Rule):
-
-
-
使用流程
-
新建一个工程
-
cd 工程中
-
新建一个爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com
-
-
样例:
1 #demo 2 # -*- coding: utf-8 -*- 3 import scrapy 4 from scrapy.linkextractors import LinkExtractor 5 from scrapy.spiders import CrawlSpider, Rule 6 7 8 class CrawlproSpider(CrawlSpider): 9 name = 'Crawlpro' 10 allowed_domains = ['www.xxx.com'] 11 start_urls = ['http://www.xxx.com/'] 12 13 rules = ( 14 Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), 15 ) 16 17 def parse_item(self, response): 18 item = {} 19 #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get() 20 #item['name'] = response.xpath('//div[@id="name"]').get() 21 #item['description'] = response.xpath('//div[@id="description"]').get() 22 return item 23
分布式:
-- 目的:
- 让多台爬虫机器同时运行爬虫任务并协同爬取,协同爬取的前提是共享爬取队列;
- 统一爬取队列,多个调度器、多个下载器----结果是爬取效率翻倍。
-- 维护爬虫队列:
- 性能考虑:基于内存存储的redis
-
列表有lpush、lpop、rpush、rpop方法,实现先进先出爬取队列,也可以实现先进后出栈式爬取队列
-
集合元素无需并且不重复
-
可实现带优先级的调度队列
-- URL地址去重:
-
使用md5生成的数据指纹来筛选数据,将转换的md5值与之前的传入redis中的数据进行比对;
-
使用哈希算法生成数据指纹筛选数据,将转换的md5值与之前的传入redis中的数据进行比对;
-
布隆过滤器
-- 文本内容去重:
-
编辑距离算法
-
simhash算法
-- 防止中断:
-
为什么要防止中断:
-
在scrapy中,爬虫运行时的Request队列放在内存中,在爬虫运行中中断后,这个空间就被释放,此队列就被销毁,所以一旦爬虫被中断,爬虫再次运行就相当于全新的爬取过程。
-
-
解决方法:
-
将队列中的Request保存起来,再次爬取直接读取保存的数据即可
-
实现命令:
scrapy crawl spider -s JOB_DIP=crawls/spider(路径使用JOB_DIP标识)
-
增量式:
-
概念:用于检测网站数据更新的情况。
-
应用的网站类型:
-
增量式深度爬取
-
增量式非深度爬取
-
-
核心机制:去重;redis-set去重方式(可做持久化存储),python中的set是基于缓存中的
-
增量式流程:
-
与基本的爬虫Scrapy流程相似,只是再spider中加了数据指纹的认证
再pipelines中增加了redis的存储过程 -
rules配置规则
-
数据库的连接
-
item数据类型创建完成
-
pipelines
-
数据库的调用:spider.方法
-
redis的存储
-
-
-
-
框架中的item传输问题(parse传到其他函数中):
-
传输:
1 yield scrapy.Resquest(url,callback=self.parse_detail,meta={'item':item})
-
下个函数接收:
1 item = response.meta['item'] 2 item[""] = ... 3 yield item
-
scrapy中的post请求:
首先创建好scrapy
文件,注释掉allowed_domains以及start_urls
;重写初始化请求(start_requests)
,最后yield
返回给解析函数。
1 class xxx(scrapy.Spider): 2 name = 'xxxx' 3 #allowed_domains = [''] 4 #start_url = [''] 5 6 def start_requests(self): 7 headers = {} 8 base_url = '' 9 formdata = {} 10 yield scrapy.FormRequest(url=base_url,headers=headers,formdata=formdata,callback=self.parse) 11 #如果使用FormRequest报错,备选方案 12 scrapy.Request(url=base_url,headers=headers,body=json.dumps(formdata),method= 'POST',callback=self.parse) 13 14 15 def parse(self,response): 16 pass
扩展:
反爬机制整理:
-
robots
-
UA伪装
-
验证码
-
代理
-
cookie
-
动态变化的请求参数
-
JS加密
-
JS混淆
-
图片懒加载
-
动态数据的获取
-
selenium:规避检测
图片懒加载:
-
网站优化手段
-
应用到标签的伪属性,数据捕获的时候一定基于伪属性进行
-
是一种反爬机制,图片懒加载是一种网页优化技术。图片作为一种网络资源,在被请求时也与普通静态资源一样,将占用网络资源,而一次性将整个页面的所有图片加载完,将大大增加页面的首屏加载时间。为了解决这种问题,通过前后端配合,使图片仅在浏览器当前视窗内出现时才加载该图片,达到减少首屏图片请求数的技术就被称为“图片懒加载”。 在网页源码中,在img标签中首先会使用一个“伪属性”(通常使用src2,original…)去存放真正的图片链接而并非是直接存放在src属性中。当图片出现到页面的可视化区域中,会动态将伪属性替换成src属性,完成图片的加载。
-
imagePileline:专门用于二进制数据下载和持久化存储的管道类(图片下载)
scrapy中技巧:
-
当调用item中的类时,没有显示且标红
-
-
1 #翻页设置 2 url = response.xpath('//span[@class="next"]/a/@href').extract_first() 3 if url !=None: 4 '''' 5 在提取数据后,parse()方法查找到下一页的链接,使用urljoin()方法构建完整的绝对URL(因为链接可以是相对的), 6 并产生一个新的请求到下一个页面,将自己作为回调函数来处理下一页的数据提取,并保持遍历所有页面的抓取。 7 ''' 8 url = response.urljoin(url) 9 yield scrapy.Request( 10 url=url,callback=self.parse 11 )
selenium-绕过网站监测:
source
:https://blog.51cto.com/u_15023263/2558748
使用 Google 的Chrome Devtools-Protocol(Chrome 开发工具协议)简称CDP。
通过这个命令,我们可以给定一段 JavaScript 代码,让 Chrome 刚刚打开每一个页面,还没有运行网站自带的 JavaScript
代码时,就先执行我们给定的这段代码。
那么如何在 Selenium
中调用 CDP
的命令呢?实际上非常简单,我们使用driver.execute_cdp_cmd
。根据 Selenium 的官方文档[2],传入需要调用的 CDP 命令和参数即可;
只需要执行一次,之后只要你不关闭这个driver开启的窗口,无论你打开多少个网址,他都会自动提前在网站自带的所有 js 之前执行这个语句,隐藏window.navigator.webdriver
。
完美隐藏window.navigator.webdriver
。并且,关键语句:
1 driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { 2 "source": """ 3 Object.defineProperty(navigator, 'webdriver', { 4 get: () => undefined 5 }) 6 """ 7 })
虽然使用以上代码就可以达到目的了,不过为了实现更好的隐藏效果,大家也可以继续加入两个实验选项:
1 from selenium import webdriver 2 options = webdriver.ChromeOptions() 3 options.add_experimental_option("excludeSwitches", ["enable-automation"]) 4 options.add_experimental_option('useAutomationExtension', False) 5 driver = webdriver.Chrome(options=options, executable_path='./chromedriver') 6 driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { 7 "source": """ 8 Object.defineProperty(navigator, 'webdriver', { 9 get: () => undefined 10 }) 11 """ 12 }) 13 driver.get('http://exercise.kingname.info')
这是close()的说明:
Closes the current window. 关闭当前窗口。
这是quit()的说明:
Quits the driver and closes every associated window. 退出驱动并关闭所有关联的窗口。
-
gb2312与gb2312 urlencode区别
1 import urllib 2 country = u'中国' 3 country.encode('gb2312') 4 #-------'\xd6\xd0\xb9\xfa' 5 urllib.quote(country.encode('gb2312')) 6 #--------'%D6%D0%B9%FA'