爬虫

/ 技术 / 无站内评论 / 436浏览

爬虫

什么是爬虫?

简单的来,使用任何技术手段,批量获取网站信息的一种方式。关键在于批量。 现在很多都是数据抓取爬虫,其实爬虫还可以模拟人为的执行一些操作,或者是重复性劳动。主要是效率,会比人实际操作高效。

爬虫理论上步骤很简单,第一步获取html源码,第二步分析html并拿到数据。但实际操作,老麻烦了~

“爬虫”需要掌握哪些知识

请求

我们常说爬虫其实就是一堆的http(s)请求,找到待爬取的链接,然后发送一个请求包,得到一个返回包,当然,也有HTTP长连接(keep-alive)或h5中基于stream的websocket协议,这里暂不考虑,所以核心的几个要素就是:

  1. url

  2. 请求header、body

  3. 响应herder、内容

URL

爬虫开始运行时需要一个初始url,然后会根据爬取到的html文章,解析里面的链接,然后继续爬取,这就像一棵多叉树,从根节点开始,每走一步,就会产生新的节点。为了使爬虫能够结束,一般都会指定一个爬取深度(Depth)。

1.广度爬虫

首先应该从一个较小集合的页面开始,由于页面中还会有其它页面的链接,其就以广度优先的策略来抓取其它链接所指向的页面。

2.垂直爬虫

所谓垂直搜索,即某些搜索引擎可能只是想关注某一领域的内容。因此,爬虫也只是抓取和主题相关的页面。

  https://b2c.csair.com/portal/minPrice/queryMinPriceInSeven

Http请求

http请求信息由请求方法(method)请求头(headers)请求正文(body)三部分组成。由于method一般是header中的第一行,也可以说请求头中包含请求方法,下面是使用Fiddler访问csair.com请求头的一部分:

  请求方法(method)
POST https://b2c.csair.com/portal/minPrice/queryMinPriceInSeven HTTP/1.1

请求头(headers)
Host: b2c.csair.com
Connection: keep-alive
Content-Length: 228
Accept: application/json, text/javascript, */*; q=0.01
Origin: https://b2c.csair.com
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Content-Type: application/json
Referer: https://b2c.csair.com/B2C40/newTrips/static/main/page/booking/index.html?t=S&c1=CAN&c2=PEK&d1=2018-11-30&at=1&ct=0&it=0
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: likev_user_id=67a16f83-e87b-46c6-b24b-1994e5b3c11d; language=zh_CN; zsluserCookie=true; last_session_stm_8mrmut7r76ntg21b=1542205920321; likev_session_id_8mrmut7r76ntg21b=902e9251-5da5-4b29-e9d1-ace8958f8f75; last_session_id_8mrmut7r76ntg21b=902e9251-5da5-4b29-e9d1-ace8958f8f75; sid=7e43ffb915254b1a8488ace5870deee7; JSESSIONID=A5C9627D59DE8C2B331EE83BD25F06C6; likev_session_etm_8mrmut7r76ntg21b=1542205930941; temp_zh=cou%3D0%3Bsegt%3D%E5%8D%95%E7%A8%8B%3Btime%3D2018-11-30%3B%E5%B9%BF%E5%B7%9E-%E5%8C%97%E4%BA%AC%3B1%2C0%2C0%3B%26; WT-FPC=id=111.172.113.157-2221653168.30690567:lv=1542205930937:ss=1542205919978:fs=1537024785776:pn=1:vn=2; WT.al_flight=WT.al_hctype(S)%3AWT.al_adultnum(1)%3AWT.al_childnum(0)%3AWT.al_infantnum(0)%3AWT.al_orgcity1(CAN)%3AWT.al_dstcity1(PEK)%3AWT.al_orgdate1(2018-11-30)

请求正文(body)
{"depCity":"CAN","arrCity":"PEK","flightDate":"2018-11-30","adultNum":"1","childNum":"0","infantNum":"0","cabinOrder":"0","airLine":"1","flyType":"3","international":"0","action":"0","segType":"1","cache":"0","channel":"B2CPC1"}

本文不会解释各个字段的意思,详细的解释请移步w3c Http Header Field Definitions . 相关的header字段如下:

链接的来源,通常在访问链接时,都要带上Referer字段,服务器会进行来源验证,后台通常会用此字段作为防盗链的依据。

后台通常会通过此字段判断用户设备类型、系统以及浏览器的型号版本。有些编程语言包里网络请求会自定义User-Agent,可以被辨别出来,爬虫中可以设置为浏览器的ua.

一般在用户登录或者某些操作后,服务端会在返回包中包含Cookie信息要求浏览器设置Cookie,没有Cookie会很容易被辨别出来是伪造请求;

也有本地通过JS,根据服务端返回的某个信息进行处理生成的加密信息,设置在Cookie里面;

抓包

常用抓包工具:Fiddler。不嫌烦的话可以使用浏览器调试功能(F12):学爬虫就是抓包,对请求和响应进行分析,用代码来模拟

首次运行Fiddler需要设置下证书,才能抓取https协议

Tools>HTTPS>capture https connects打勾>1、2打勾

警告:为了稳定抓包,请使用chrome浏览器。

进行抓包

请求过滤

因Fiddler抓包到的是计算机上所有请求,可能请求会有点多。我们可以根据需求设置只要抓包的域名。

扩展

如何识别同一个用户?

Cookie机制
思考一下服务端如何识别特定的客户?

这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。

方便用户

设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。

总结一下

如上所知,Cookie作用非常重要。如果抓取的时候没有考虑它,或者没有带上它。服务器会觉得我们每个请求都是新的,都是独立的。那么请求得到的数据也许就是不是你想要的了。切记。

参考:https://blog.csdn.net/itguangit/article/details/78330761

代码

这里推荐下网络请求库,okhttp。

实现:模拟登陆博客后台,并且获取文章列表

思路

1.浏览器登录一遍,并且用Fiddler记录下抓包数据

2.分析抓包数据

​ 一般登录接口都是login字眼,不用想。经过分析,发现请求方法为post,参数有三个,分别是

​ username

​ password

​ remeberMe

​ 返回值为"success":true 即为成功。

3.代码模拟实现
  
      OkHttpClient ok = new OkHttpClient();
      FormBody body = new FormBody.Builder()
              .add("username", "xxx")
              .add("password", "xxx")
              .add("remeberMe", "on")
              .build();

      Request build = new Request.Builder().url("https://sanii.cn/admin/login").post(body).build();
      Response response = ok.newCall(build).execute();
      System.out.println(response.body().string());

牛逼,一切在我们掌握之中,下一步我们抓取后台的文章信息

经过抓包分析文档地址是https://sanii.cn/admin/article

来,我们继续抓。

  
      Request article = new Request.Builder()
              .url("https://sanii.cn/admin/article")
              .build();
      Response articleResponse = ok.newCall(article).execute();
      System.out.println(articleResponse.body().string());

可是 !!!这次就很奇怪了,明明我们登录成功了,为什么访问后台文章页会显示我们没有登录呢?

这时候就我们上面讲到的一个东西!Cookie。其实很多网络请求库,每次请求都是无状态的,每一次都是独立的访问。因此,我们要加上两步!

​ 1.获取登录成功时的cookie

​ 2.下次访问带上cookie

来,我们继续搞

  
List<String> headers = response.headers("Set-Cookie");
StringBuilder sb=new StringBuilder();
for (String s:headers){
  sb.append(s+"; ");
}
Request article = new Request.Builder()
      .url("https://sanii.cn/admin/article")
      .addHeader("Cookie",sb.toString())
      .build();
Response articleResponse = ok.newCall(article).execute();
System.out.println(articleResponse.body().string());

自动携带cookie

这次发现我们可以抓到数据了!!!在日常抓取中也一样,我们要很重视cookie,尤其是涉及登录相关的操作。

可能你会想到了,每次访问都要手动获取cookie吗?其实框架已经给出了接口,自己配置下就行了。下面给出okhttp和HTTPClient的配置方法。

okhttp

自动携带,保存和更新Cookie。

  
privatefinal HashMap> cookieStore =newHashMap<>();

OkHttpClient okHttpClient =new OkHttpClient.Builder()

    .cookieJar(new CookieJar() {

        @Override

        publicvoidsaveFromResponse(HttpUrl httpUrl, List list) {

            cookieStore.put(httpUrl.host(), list);

        }

        @Override

        publicList loadForRequest(HttpUrl httpUrl) {

            List cookies = cookieStore.get(httpUrl.host());

            returncookies !=null? cookies :newArrayList();

        }

    })

    .build();
HTTPClient
  
//创建一个HttpContext对象,用来保存Cookie
HttpClientContext httpClientContext = HttpClientContext.create();
//建立一个新的httpclient 请求
CloseableHttpClient httpClient = HttpClients.createDefault();
//每次执行execute方法时带上httpClientContext即可。
client.execute(request,httpClientContext);

内容分析提取

请求headers的Accept-Encoding字段表示浏览器告诉服务器自己支持的压缩算法(目前最多的是gzip),如果服务器开启了压缩,返回时会对响应体进行压缩,爬虫需要自己解压;

过去我们常需要获取的内容主要来源于网页html文档本身,也就是说,我们决定进行抓取的时候,都是html中包含的内容,但是随着这几年web技术飞速的发展,动态网页越来越多,尤其是移动端,大量的SPA应用,这些网站中大量的使用了ajax技术。我们在浏览器中看到的网页已不全是html文档说包含的,很多都是通过javascript动态生成的,一般来说,我们最终眼里看到的网页包括以下三种:

Html文档本身包含内容

这种情况是最容易解决的,一般来讲基本上是静态网页已经写死的内容,或者动态网页,采用模板渲染,浏览器获取到HTML的时候已经是包含所有的关键信息,所以直接在网页上看到的内容都可以通过特定的HTML标签得到。这种情况解析也是很简单的,一般的方法有一下几种:

http://flights.sichuanair.com

http://flights.sichuanair.com/3uair/ibe/ticket/pek-ctu.html

  1. CSS选择器

  2. XPATH(这个值得学习一下)

  3. 正则表达式或普通字符串查找

  4. JavaScript代码加载内容

一般来说有两种情况:一种情况是在请求到html文档时,网页的数据在js代码中,而并非在html标签中,之所以我们看到的网页是正常的,那是因为,其实是由于执行js代码动态添加到标签里面的,所以这个时候内容在js代码里面的,而js的执行是在浏览器端的操作,所以用程序去请求网页地址的时候,得到的response是网页代码和js的代码,所以自己在浏览器端能看到内容,解析时由于js未执行,肯定找到指定HTML标签下内容肯定为空,如百度的主页就是这种,这个时候的处理办法,一般来讲主要是要找到包含内容的js代码串,然后通过正则表达式获得相应的内容,而不是解析HTML标签。另一种情况是在和用户交互时,JavaScript可能会动态生成一些dom,如点击某个按钮弹了一个对话框等;对于这种情况,一般这些内容都是一些用户提示相关的内容,没什么价值,如果确实需要,可以分析一下js执行逻辑,但这样的情况很少。

https://www.hnair.com/

https://new.hnair.com/hainanair/ibe/air/searchResults.do

Ajax/Fetch异步请求

一般返回体为Json的形式,现在主流都是前后分离开发,比较新的项目请求都是返回Json。

这种情况是现在很常见的,尤其是在内容以分页形式显示在网页上,并且页面无刷新,或者是对网页进行某个交互操作后,得到内容。对于这种页面,分析的时候我们要跟踪所有的请求,观察数据到底是在哪一步加载进来的。然后当我们找到核心的异步请求的时候,就只需抓取这个异步请求就可以了,如果原始网页没有任何有用信息,也没必要去抓取原始网页了。

https://b2c.csair.com/portal/flight/direct/query

爬虫技术的现状

语言

理论上来说,任何支持网络通信的语言都是可以写爬虫的,爬虫本身虽然语言关系不大,但是,总有相对顺手、简单的。目前来说,大多数爬虫是用后台脚本类语言写的,其中python无疑是用的最多最广的,并且页诞生了很多优秀的库和框架,如scrapy、BeautifulSoup 、pyquery、Mechanize等。但是一般来说,搜索引擎的爬虫对爬虫的效率要求更高,会选用c++、java、go(适合高并发),详情 排名前50的开源Web爬虫用于数据挖掘.

运行环境

爬虫本身不区分到底是运行在windows还是Linux,又或是OSX,但从业务角度讲,我们把运行在服务端(后台)的,称之为后台爬虫。而现在,几乎所有的爬虫都是后台爬虫。

后台爬虫的三大问题

后台爬虫在大行其道的时候,也有着些许棘手的、到目前也没有什么好的解决方案问题,而归根结底,这些问题的根本原因是由于后台爬虫的先天不足导致,在正式讨论之前,我们先思考一个问题,“爬虫和浏览器有什么异同?”。

相同点

本质上都是通过http/https协议请求互联网数据

不同点

  1. 爬虫一般为自动化程序,无需用用户交互,而浏览器不是

  2. 运行场景不同;浏览器运行在客户端,而爬虫一般都跑在服务端

  3. 能力不同;浏览器包含渲染引擎、javascript虚拟机,而爬虫一般都不具备这两者。

了解了这些,我们再来看看后台面临的问题

问题一:交互问题

有些网页往往需要和用户进行一些交互,进而才能走到下一步,比如输入一个验证码,拖动一个滑块,选几个汉字。网站之所以这么做,很多时候都是为了验证访问者到底是人还是机器。而爬虫程序遇到这种情况很难处理,传统的简单图片验证码可以通过图形处理算法读出内容,但是随着各种各样,花样百出,人神共愤的、变态的验证码越来越多(尤其是买火车票时,分分钟都想爆粗口),这个问题就越来越严重。

解决

​ 市场上有很多验证码破解接口,花钱。

https://www.juhe.cn/docs/api/id/60 1W次大概300RMB

问题二:Javascript 解析问题

如前文所述,javascript可以动态生成dom。目前大多数网页属于动态网页(内容由javascript动态填充),尤其是在移动端,SPA/PWA应用越来越流行,网页中大多数有用的数据都是通过ajax/fetch动态获取后然后再由js填充到网页dom树中,单纯的html静态页面中有用的数据很少。目前主要应对的方案就是对于js ajax/fetch请求直接请求ajax/fetch的url ,但是还有一些ajax的请求参数会依赖一段javascript动态生成,比如一个请求签名,再比如用户登陆时对密码的加密等等,如果一昧的去用后台脚本去干javascript本来做的事,这就要清楚的理解原网页代码逻辑,而这不仅非常麻烦,而且会使你的爬取代码异常庞大臃肿,但是,更致命的是,有些javascript可以做的事爬虫程序是很难甚至是不能模仿的,比如有些网站使用拖动滑块到某个位置的验证码机制,这就很难再爬虫中去模仿。

其实,总结一些,这些弊端归根结底,是因为爬虫程序并非是浏览器,没有javascript解析引擎所致。针对这个问题,目前主要的应对策略就是在爬虫中引入Javascript 引擎,如PhantomJS,但是又有着明显的弊端,如服务器同时有多个爬取任务时,资源占用太大。还有就是,这些 无窗口的javascript引擎很多时候使用起来并不能像在浏览器环境中一样,页面内部发生跳转时,会导致流程很难控制。

解决

​ 使用自动化测试工具模仿加载,列如:SelenIUM

https://wizardforcel.gitbooks.io/selenium-doc/content/official-site/introduction.html

问题三:IP限制

这是目前对后台爬虫中最致命的。网站的防火墙会对某个固定ip在某段时间内请求的次数做限制,如果没有超过上线则正常返回数据,超过了,则拒绝请求,如qq 邮箱。值得说明的是,ip限制有时并非是专门为了针对爬虫的,而大多数时候是出于网站安全原因针对DOS攻击的防御措施。后台爬取时机器和ip有限,很容易达到上线而导致请求被拒绝。目前主要的应对方案是使用代理,这样一来ip的数量就会多一些,但代理ip依然有限,对于这个问题,根本不可能彻底解决。

四川航空

解决

​ 花钱买IP代理,否则自己搭建IP代理服务器。

https://cuiqingcai.com/5094.html

召唤蕾姆
琼ICP备18000156号

鄂公网安备 42011502000211号