Python爬虫

Scrapy Engine: 这是引擎,负责Spiders、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等等!(像不像人的身体?)

Scheduler(调度器): 它负责接受引擎发送过来的requests请求,并按照一定的方式进行整理排列,入队、并等待Scrapy Engine(引擎)来请求时,交给引擎。

Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spiders来处理,

Spiders:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),

Item Pipeline:它负责处理Spiders中获取到的Item,并进行处理,比如去重,持久化存储(存数据库,写入文件,总之就是保存数据用的)

Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件

数据在整个Scrapy的流向:

程序运行的时候,

引擎:Hi!Spider, 你要处理哪一个网站?

Spiders:我要处理23wx.com

引擎:你把第一个需要的处理的URL给我吧。

Spiders:给你第一个URL是XXXXXXX.com

引擎:Hi!调度器,我这有request你帮我排序入队一下。

调度器:好的,正在处理你等一下。

引擎:Hi!调度器,把你处理好的request给我,

调度器:给你,这是我处理好的request

引擎:Hi!下载器,你按照下载中间件的设置帮我下载一下这个request

下载器:好的!给你,这是下载好的东西。(如果失败:不好意思,这个request下载失败,然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载。)

引擎:Hi!Spiders,这是下载好的东西,并且已经按照Spider中间件处理过了,你处理一下(注意!这儿responses默认是交给def parse这个函数处理的)

Spiders:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,这是我需要跟进的URL,将它的responses交给函数 def xxxx(self, responses)处理。还有这是我获取到的Item。

引擎:Hi !Item Pipeline 我这儿有个item你帮我处理一下!调度器!这是我需要的URL你帮我处理下。然后从第四步开始循环,直到获取到你需要的信息,

注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy会重新下载。)

scrapy的去重原理流程:利用hash值和集合去重。首先创建fingerprint = set()结合,然后将request对象利用sha1对象进行信息摘要,摘要完成之后, 判断hash值是否在集合中,如果在,返回true,如果不在,就add到集合。

scrapy-redis 的去重原理基本是一样的,只不过持久化存储到redis共享数据库中,当请求数据达到10亿级以上,这个时候就会非常消耗内存,一个sha1 40个字节,就会占40G的内存,这个存储绝大部分的数据库无法承受,这个时候就要使用布隆过滤器。

scrapy 是一个快速(fast)、高层次(high-level)的基于 python 的 web 爬虫构架,用于抓取web站点并从页面中提取结构化的数据。scrapy 使用了 Twisted异步网络库来处理网络通讯。

它更容易构建大规模的抓取项目
它异步处理请求,速度非常快
它可以使用自动调节机制自动调整爬行速度

可以借助scrapy_redis类库来实现。

在分布式爬取时,会有master机器和slave机器,其中,master为核心服务器,slave为具体的爬虫服务器。

我们在master服务器上搭建一个redis数据库,并将要抓取的url存放到redis数据库中,所有的slave爬虫服务器在抓取的时候从redis数据库中去链接,由于scrapy_redis自身的队列机制,slave获取的url不会相互冲突,然后抓取的结果最后都存储到数据库中。master的redis数据库中还会将抓取过的url的指纹存储起来,用来去重。相关代码在dupefilter.py文件中的request_seen()方法中可以找到。

去重问题:
dupefilter.py 里面的源码:
def request_seen(self, request):
fp = request_fingerprint(request)
added = self.server.sadd(self.key, fp)
return not added
去重是把 request 的 fingerprint 存在 redis 上,来实现的。

requests 是 polling 方式的,会被网络阻塞,不适合爬取大量数据

scapy 底层是异步框架 twisted ,并发是最大优势

对于IO密集型代码(文件处理,网络爬虫),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,会造成不必要的时间等待,而开启多线程后,A线程等待时,会自动切换到线程B,可以不浪费CPU的资源,从而提升程序执行效率)。

在实际的采集过程中,既考虑网速和相应的问题,也需要考虑自身机器硬件的情况,来设置多进程或者多线程。

网络请求:urllib,requests,aiohttp
数据解析:re,xpath,bs4,pyquery
selenium
js逆向:pyexcJs

动态加载的数据
动态变化的请求参数
js加密
代理
cookie

基于抓包工具进行全局搜索
如果动态加载的数据是密文,则全局搜索是搜索不到

fiddler,appnium,网络配置

基于手动请求发送+递归解析
基于CrwalSpider(LinkExtractor,Rule)

使用框架
线程池,多任务的异步协程
分布式

从功能上来讲,爬虫一般分为数据采集,处理,储存三个部分。这里我们只讨论数据采集部分。

一般网站从三个方面反爬虫:用户请求的Headers,用户行为,网站目录和数据加载方式。前两种比较容易遇到,大多数网站都从这些角度来反爬虫。第三种一些应用ajax的网站会采用,这样增大了爬取的难度。

1)通过Headers反爬虫

从用户请求的Headers反爬虫是最常见的反爬虫策略。很多网站都会对Headers的User-Agent进行检测,还有一部分网站会对Referer进行检测(一些资源网站的防盗链就是检测Referer)。如果遇到了这类反爬虫机制,可以直接在爬虫中添加Headers,将浏览器的User-Agent复制到爬虫的Headers中;或者将Referer值修改为目标网站域名。对于检测Headers的反爬虫,在爬虫中修改或者添加Headers就能很好的绕过。

2)基于用户行为反爬虫

还有一部分网站是通过检测用户行为,例如同一IP短时间内多次访问同一页面,或者同一账户短时间内多次进行相同操作。

大多数网站都是前一种情况,对于这种情况,使用IP代理就可以解决。可以专门写一个爬虫,爬取网上公开的代理ip,检测后全部保存起来。这样的代理ip爬虫经常会用到,最好自己准备一个。有了大量代理ip后可以每请求几次更换一个ip,这在requests或者urllib2中很容易做到,这样就能很容易的绕过第一种反爬虫。

对于第二种情况,可以在每次请求后随机间隔几秒再进行下一次请求。有些有逻辑漏洞的网站,可以通过请求几次,退出登录,重新登录,继续请求来绕过同一账号短时间内不能多次进行相同请求的限制。

3)动态页面的反爬虫

上述的几种情况大多都是出现在静态页面,还有一部分网站,我们需要爬取的数据是通过ajax请求得到,或者通过JavaScript生成的。首先用Firebug或者HttpFox对网络请求进行分析。如果能够找到ajax请求,也能分析出具体的参数和响应的具体含义,我们就能采用上面的方法,直接利用requests或者urllib2模拟ajax请求,对响应的json进行分析得到需要的数据。

能够直接模拟ajax请求获取数据固然是极好的,但是有些网站把ajax请求的所有参数全部加密了。我们根本没办法构造自己所需要的数据的请求。我这几天爬的那个网站就是这样,除了加密ajax参数,它还把一些基本的功能都封装了,全部都是在调用自己的接口,而接口参数都是加密的。遇到这样的网站,我们就不能用上面的方法了,我用的是selenium+phantomJS框架,调用浏览器内核,并利用phantomJS执行js来模拟人为操作以及触发页面中的js脚本。从填写表单到点击按钮再到滚动页面,全部都可以模拟,不考虑具体的请求和响应过程,只是完完整整的把人浏览页面获取数据的过程模拟一遍。

用这套框架几乎能绕过大多数的反爬虫,因为它不是在伪装成浏览器来获取数据(上述的通过添加 Headers一定程度上就是为了伪装成浏览器),它本身就是浏览器,phantomJS就是一个没有界面的浏览器,只是操控这个浏览器的不是人。利用 selenium+phantomJS能干很多事情,例如识别点触式(12306)或者滑动式的验证码,对页面表单进行暴力破解等等。它在自动化渗透中还 会大展身手,以后还会提到这个。

默认情况下scrapy是深度优先。
深度优先:占用空间大,但是运行速度快。
广度优先:占用空间少,运行速度慢

无头浏览器即headless browser,是一种没有界面的浏览器。既然是浏览器那么浏览器该有的东西它都应该有,只是看不到界面而已。

Python中selenium模块中的PhantomJS即为无界面浏览器(无头浏览器):是基于QtWebkit的无头浏览器。

优点:
scrapy 是异步的
采取可读性更强的xpath代替正则
强大的统计和log系统
同时在不同的url上爬行
支持shell方式,方便独立调试
写middleware,方便写一些统一的过滤器
通过管道的方式存入数据库

缺点:基于python的爬虫框架,扩展性比较差
基于twisted框架,运行中的exception是不会干掉reactor,并且异步框架出错后是不会停掉其他任务的,数据出错后难以察觉。

解决限制IP可以使用代理IP地址池、服务器;不适用动态爬取的情况下可以使用反编译JS文件获取相应的文件,或者换用其它平台(比如手机端)看看是否可以获取相应的json文件。

1.输入式验证码
解决思路:这种是最简单的一种,只要识别出里面的内容,然后填入到输入框中即可。这种识别技术叫OCR,这里我们推荐使用Python的第三方库,tesserocr。对于没有什么背影影响的验证码,直接通过这个库来识别就可以。但是对于有嘈杂的背景的验证码这种,直接识别识别率会很低,遇到这种我们就得需要先处理一下图片,先对图片进行灰度化,然后再进行二值化,再去识别,这样识别率会大大提高。

验证码识别大概步骤:
转化成灰度图
去背景噪声
图片分割

2.滑动式验证码
解决思路:对于这种验证码就比较复杂一点,但也是有相应的办法。我们直接想到的就是模拟人去拖动验证码的行为,点击按钮,然后看到了缺口 的位置,最后把拼图拖到缺口位置处完成验证。

第一步:点击按钮。然后我们发现,在你没有点击按钮的时候那个缺口和拼图是没有出现的,点击后才出现,这为我们找到缺口的位置提供了灵感。

第二步:拖到缺口位置。我们知道拼图应该拖到缺口处,但是这个距离如果用数值来表示?通过我们第一步观察到的现象,我们可以找到缺口的位置。这里我们可以比较两张图的像素,设置一个基准值,如果某个位置的差值超过了基准值,那我们就找到了这两张图片不一样的位置,当然我们是从那块拼图的右侧开始并且从左到右,找到第一个不一样的位置时就结束,这是的位置应该是缺口的left,所以我们使用selenium拖到这个位置即可。这里还有个疑问就是如何能自动的保存这两张图?这里我们可以先找到这个标签,然后获取它的location和size,然后 top,bottom,left,right = location[‘y’] ,location[‘y’]+size[‘height’]+ location[‘x’] + size[‘width’] ,然后截图,最后抠图填入这四个位置就行。具体的使用可以查看selenium文档,点击按钮前抠张图,点击后再抠张图。最后拖动的时候要需要模拟人的行为,先加速然后减速。因为这种验证码有行为特征检测,人是不可能做到一直匀速的,否则它就判定为是机器在拖动,这样就无法通过验证了。

3.点击式的图文验证 和 图标选择

图文验证:通过文字提醒用户点击图中相同字的位置进行验证。

图标选择: 给出一组图片,按要求点击其中一张或者多张。借用万物识别的难度阻挡机器。

这两种原理相似,只不过是一个是给出文字,点击图片中的文字,一个是给出图片,点出内容相同的图片。

这两种没有特别好的方法,只能借助第三方识别接口来识别出相同的内容,推荐一个超级鹰,把验证码发过去,会返回相应的点击坐标。

然后再使用selenium模拟点击即可。具体怎么获取图片和上面方法一样。

破解核心思路:
1、如何确定滑块滑动的距离?
滑块滑动的距离,需要检测验证码图片的缺口位置
滑动距离 = 终点坐标 – 起点坐标
然后问题转化为我们需要屏幕截图,根据selenium中的position方法并进行一些坐标计算,获取我们需要的位置

2、坐标我们如何获取?
起点坐标:
每次运行程序,位置固定不变,滑块左边界离验证码图片左边界有6px的距离
终点坐标:
每次运行程序,位置会变,我们需要计算每次缺口的位置
怎么计算终点也就是缺口的位置?
先举个例子,比如我下面两个图片都是120×60的图片,一个是纯色的图片,一个是有一个蓝色线条的图片(蓝色线条位置我事先设定的是60px位置),我现在让你通过程序确定蓝色线条的位置,你怎么确定?

答案:
遍历所有像素点色值,找出色值不一样的点的位置来确定蓝色线条的位置
这句话该怎么理解?大家点开我下面的图片,是不是发现图片都是由一个一个像素点组成的,120×60的图片,对应的像素就是横轴有120个像素点,纵轴有60个像素点,我们需要遍历两个图片的坐标并对比色值,从(0,0)(0,1)……一直到(120,60),开始对比两个图片的色值,遇到色值不一样的,我们return返回该位置即可

以json格式存储到文本文件
这是最简单,最方便,最使用的存储方式,json格式保证你在打开文件时,可以直观的检查所存储的数据,一条数据存储一行,这种方式适用于爬取数据量比较小的情况,后续的读取分析也是很方便的。

存储到excel
如果爬取的数据很容易被整理成表格的形式,那么存储到excel是一个比较不错的选择,打开excel后,对数据的观察更加方便,excel也可以做一些简单的操作,写excel可以使用xlwt这个库,读取excel可以使用xlrd,同方法1一样,存储到excel里的数据不宜过多,此外,如果你是多线程爬取,不可能用多线程去写excel,这是一个限制。

存储到sqlite
sqlite无需安装,是零配置数据库,这一点相比于mysql要轻便太多了,语法方面,只要你会mysql,操作sqlite就没有问题。当爬虫数据量很大时,需要持久化存储,而你又懒得安装mysql时,sqlite绝对是最佳选择,不多呢,它不支持多进程读写,因此不适合多进程爬虫。

存储到mysql数据库
mysql可以远程访问,而sqlite不可以,这意味着你可以将数据存储到远程服务器主机上,当数据量非常大时,自然要选择mysql而不是sqlite,但不论是mysql还是sqlite,存储数据前都要先建表,根据要抓取的数据结构和内容,定义字段,这是一个需要耐心和精力的事情。

存储到mongodb
我最喜欢no sql 数据库的一个原因就在于不需要像关系型数据库那样去定义表结构,因为定义表结构很麻烦啊,要确定字段的类型,varchar 类型数据还要定义长度,你定义的小了,数据太长就会截断。
mongodb 以文档方式存储数据,你使用pymongo这个库,可以直接将数据以json格式写入mongodb, 即便是同一个collection,对数据的格式也是没有要求的,实在是太灵活了。
刚刚抓下来的数据,通常需要二次清洗才能使用,如果你用关系型数据库存储数据,第一次就需要定义好表结构,清洗以后,恐怕还需要定义个表结构,将清洗后的数据重新存储,这样过于繁琐,使用mongodb,免去了反复定义表结构的过程。

这时候就需要cookie自动的更新了。通常怎样自动更新cookie呢?这里会用到selenium。
步骤1、 采用selenium自动登录获取cookie,保存到文件;
步骤2、 读取cookie,比较cookie的有效期,若过期则再次执行步骤1;
步骤3、 在请求其他网页时,填入cookie,实现登录状态的保持。

selenium
Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等主流浏览器。这个工具的主要功能包括:测试与浏览器的兼容性——测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。

它的功能有:
框架底层使用JavaScript模拟真实用户对浏览器进行操作。测试脚本执行时,浏览器自动按照脚本代码做出点击,输入,打开,验证等操作,就像真实用户所做的一样,从终端用户的角度测试应用程序。
使浏览器兼容性测试自动化成为可能,尽管在不同的浏览器上依然有细微的差别。
使用简单,可使用Java,Python等多种语言编写用例脚本
也就是说,它可以根据指令,做出像真实的人在访问浏览器一样的动作,比如打开网页,截图等功能。

phantomjs
(新版本的selenium已经开始弃用phantomjs, 不过有时候我们可以单独用它做一些事情)

是一个基于Webkit的无界面浏览器,可以把网站内容加载到内存中并执行页面上的各种脚本(比如js)。

1、304页面http状态码

当第二次请求页面访问的时候,该页面如果未更新,则会反馈一个304代码,而搜索引擎也会利用这个304http状态码来进行判断页面是否更新。

首先第一次肯定是要爬取网页的,假设是A.html,这个网页存储在磁盘上,相应地有个修改时间(也即是更新这个文件的时间)。

那么第二次爬取的时候,如果发现这个网页本地已经有了,例如A.html,这个时候,你只需要向服务器发送一个If-Modified-Since的请求,把A.html的修改时间带上去。

如果这段时间内,A.html更新了,也就是A.html过期了,服务器就会HTTP状态码200,并且把新的文件发送过来,这时候只要更新A.html即可。

如果这段时间内,A.html的内容没有变,服务器就会返返回HTTP状态码304(不返回文件内容),这个时候就不需要更新文件。

2、Last-Modified文件最后修改时间

这是http头部信息中的一个属性,主要是记录页面最后一次的修改时间,往往我们会发现,一些权重很高的网站,及时页面内容不更新,但是快照却还是能够每日更新,这其中就有Last-Modified的作用。通产情况下,下载网页我们使用HTTP协议,向服务器发送HEAD请求,可以得到页面的最后修改时间LastModifed,或者标签ETag。将这两个变量和上次下载记录的值的比较就可以知道一个网页是否跟新。这个策略对于静态网页是有效的。是对于绝大多数动态网页如ASP,JSP来说,LastModifed就是服务器发送Response的时间,并非网页的最后跟新时间,而Etag通常为空值。所以对于动态网页使用LastModifed和Etag来判断是不合适的,因此Last-Modified只是蜘蛛判断页面是否更新的一个参考值,而不是条件。

Original: https://www.cnblogs.com/sunlincode/p/16742545.html
Author: 小孙不是程序员
Title: Python爬虫

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/579604/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Java Servlet(十二):Servlet、Listener、Filter之间的执行流程分析

    时隔几年后,看到本系列文章讲解的内容缺少了不少内容:周末无事分析了Spring Security是如何被集成到Web Servlet(SpringMVC)时,需要重新理清Filte…

    Java 2023年5月29日
    084
  • 机器学习实战-决策树

    1.决策树的构造 1.1优缺点 优点: 计算复杂度不高:以ID3为例,每次运算都是基于某一列特征,特征计算完后,下次计算不考虑该最有特征,并且通过适当剪枝可以简化复杂度 输出结果易…

    Java 2023年6月14日
    076
  • 亚信防毒墙网络版卸载

    许多大公司(尤其是央企)都统一安装亚信防毒墙网络版,但亚信在使用中非常不友好,功能差、内存占比高,每个人都有干掉它的冲动,但卸载亚信防毒墙网络版需要本地管理员密码,多数人面对亚信防…

    Java 2023年5月30日
    0239
  • Spring和Springboot相关知识点整理

    简介 本文主要整理一些Spring & SpringBoot应用时和相关原理的知识点,对于源码不做深入的讲解。 思维导图 右键新窗口打开可以放大。 说明 使用@Config…

    Java 2023年5月30日
    074
  • Java IO理解

    Java BIO NIO AIO理解 同步 异步 同步:发起一个调用后,被调用者未处理完请求之前,不返回 异步:发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者…

    Java 2023年6月8日
    070
  • JAVA_AesCBC例子

    java;gutter:true; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; im…

    Java 2023年5月29日
    063
  • 自定义线程池的创建与@Async注解使用

    1.创建线程池配置 2.使用 @Async介绍 在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完…

    Java 2023年6月8日
    079
  • 【RocketMQ】MQ消息发送

    消息发送 首先来看一个RcoketMQ发送消息的例子: @Service public class MQService { @Autowired DefaultMQProducer…

    Java 2023年6月8日
    047
  • nginx http和https共存

    server { listen 80 default backlog=2048; listen 443 ssl; server_name linuxyan.com; root /v…

    Java 2023年5月30日
    071
  • 面试常问的dubbo的spi机制到底是什么?

    前言 dubbo是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。作为spring cloud alibaba体系中重要的一部分,随着spring clou…

    Java 2023年6月16日
    0101
  • Python3 字典浅析

    字典是一个无序、可变和有索引的集合。在 Python 中,字典用花括号编写,拥有键和值。 创建并打印字典: thisdict = { "brand": &quo…

    Java 2023年6月9日
    075
  • 一本软考教材,治好了我多年的低血压

    事情是这样的,最近想要考一个高级软件资格证书,于是二话不说买了”信息系统项目管理师”相关资料就开始学起来了。教材一到手,刚翻开第一页读了个序言,曾经熟悉的味…

    Java 2023年6月15日
    047
  • 8000字长文让你彻底了解 Java 8 的 Lambda、函数式接口、Stream 用法和原理

    我是风筝,公众号「古时的风筝」。一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农!文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白…

    Java 2023年5月29日
    079
  • eclipse快捷键大全

    eclipse快捷键大全 /* * Eclipse中的快捷键&…

    Java 2023年6月6日
    079
  • spring整合swagger2

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/yanan7890/p/15536517.htmlAut…

    Java 2023年5月30日
    075
  • Spring 5 源码解析- AOP原理分析-5

    这里以 配置文件(UserAOPTest-context.xml)信息: xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst…

    Java 2023年6月6日
    057
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球