可爱的爬虫(三)——调试器curl

可爱的爬虫(三)——调试器curl

curl,让爬虫可以在命令行里跳出浏览器的舞姿。

1 wget的尴尬

wget可以带我们浪迹天涯却不能笑傲江湖。wget张开大嘴等待着待抓取的URL,我们把浏览器地址栏中的URL喂给他,相信美好的事情总会发生。结果,一脸懵逼,杂乱的HTML中却找不到一丝数据的踪影。

我门从浏览器中看到了美丽新世界,wget却说啥也没有,你是不是逗我。这便是wget的尴尬,同样的URL却不能得到与浏览器一致的数据。

比如,淘宝手机版“m.taobao.com”,检索“在你身边为你设计”会跳到这个页面:

执行wget -q -O- URL,抓下来是这样的(URL复制自浏览器):

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>搜索宝贝</title>
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
    <meta content="yes" name="apple-mobile-web-app-capable" />
    <meta content="black" name="apple-mobile-web-app-status-bar-style" />
    <meta name="format-detection" content="telephone=no" />
    <link rel="stylesheet" href="//g.alicdn.com/tb/searchh5/0.5.7/searchlist/searchlist.css"/>
    <script type="text/javascript">
        window.SearchH5={
            apiConfig:{
                //公有参数
                params:{
                    'event_submit_do_new_search_auction': '1',
                     '_input_charset': 'utf-8',
                     'topSearch': '1',
                     'atype': 'b',
                     'searchfrom': '1',
                     'action': 'home:redirect_app_action',
                     'from': '1',
                     'q': '在你身边为你设计',
                     'sst':'1',
                'n':20,
                'buying':'buyitnow'
            },
            // 列表数据url与其需要携带的参数
            listApi:{
                url:"//s.m.taobao.com/search?",
                    params:{
                    m:'api4h5',
                    abtest:'21',
                    wlsort:'21'
                }
            },
            navApi:{
                url:'//s.m.taobao.com/browse_nav_iphone.htm',
                    params:{}
                }
            }
        };
    </script>
</head>

<body class=""><script>
with(document)with(body)with(insertBefore(createElement("script"),firstChild))setAttribute("exparams","category=&userid=&aplus&yunid=",id="tb-beacon-aplus",src=(location>"https"?"//s":"//a")+".tbcdn.cn/s/aplus_v2.js")
</script>

<!-- 手淘安装下载脚本 -->
<script src="//g.alicdn.com/tb/tracker/1.0.14/index.js"></script>

<script>JSTracker.config('sampling', 1000);</script>
<script src="//g.alicdn.com/mtb/app-jump/0.5.8/??jump_css.js,combo.js,jump.js"></script>
<!-- smartbanner -->
<script src="//h5.m.taobao.com/js/smartbanner/setting.js?t=20160615"></script>
<script src="//gw.alicdn.com/L1/584/183486/74a6066df1.js?t=20160615"></script>
<script src="//g.alicdn.com/mtb/??lib-
env/1.5.0/env.js,lib-httpurl/1.3.2/httpurl.js,lib-callapp/1.4.2/callapp.js,lib-smartbanner/0.4.0/smartbanner.js,lib-smartbanner/0.4.0/bis.js"></script>
<script src="//g.alicdn.com/sd/pointman/app/tb-h5/sufei-tb-h5.js"></script>
<script src="//g.alicdn.com/tb/searchh5/0.5.7/searchlist/searchlist.js"></script>
<script>$.use("searchH5/pages/searchlist/_.index");</script>

</body>
</html>

为什么同样的URL,wget却得不到与浏览器一致的数据?

2 浏览器的心智

浏览器在访问URL的时候,还会附加上一些额外的信息。正是这些附加信息,导致了不一样的结果。如果把网站比作一个计算的黑盒子,现在的情况是输入不一样了,所以输出也不一样就没什么好奇怪的了。

浏览器这绝对不是闲着没事儿找事儿,她的精神和扶老奶奶过马路是一样一样的。正是有了她的多管闲事添加了附加信息,才有了更加缤纷多彩的Web世界。

附加信息的官方名字叫做“HTTP首部”(HTTP header),首部由多个字段(HTTP header field)组成,每一个字段会对应一个值。经常使用的字段有User-Agent、Cookie、Set-Cookie等,所有的字段见:https://en.wikipedia.org/wiki/List_of_HTTP_header_fields

一起唠一唠常用的这几个字段。

1) User-Agent

不同的浏览器拥有不同的User-Agent,通过“www.whatsmyua.com”可以看到自己的User-Agent。有些财大气粗的网站为了提升用户体验,会开发两个版本,分别用于适配移动端和PC端,比如“m.taobao.com”和“www.taobao.com”。对于开发Web服务器的小伙伴来说,可以通过判断浏览器发过来的User-Agent来返回响应的页面。

2) Cookie与Set-Cookie

没有Cookie的年代,网站无法区分当前的访问者。于是,Cookie应运而生,浏览器在访问网站的时候,网站在返回页面的同时在响应(Response)的首部中加入Set-Cookie: XXX。浏览器收到后便将XXX藏了起来,在今后访问该网站的时候,请求(Request)的首部中会加入Cookie: XXX,这样网站那边便能够区分访问者了。一旦能够有一个手段区分访问者,便可以提供个性化访问啦。通常,网站会提供登陆的机制来验证访问者,以判断其是否有资格访问该服务。所以,在这种情况下,我们需要妥善保管每个网站发给我们的Cookie值,以免造成隐私泄露。

不知不觉中,浏览器做了这么多事情。如果把浏览器修改数据(首部字段)比作网站的“阳谋”,那么网站的“阴谋”也多着呢。比如,修改网页骨架(DOM)、网页样式(CSS)、网页跳转等等。当网站的后端开发者在搞“阳谋”的时候,其前端开发者也开始用JavaScript来搞“阴谋”了。最恐怖的是他们阴阳合璧,黑白双煞,玉女心经。

所以,我们不得不分析他们的前后端逻辑,恰到好处添加首部字段,完美模拟JavaScript的执行逻辑,让爬虫可以在命令行里跳出浏览器的舞姿。

3 调试器curl

2.1 curl上手

curl,和wget一样,可以用作下载器, 但他的优势远不止于此。当我们需要搞清楚网站的逻辑,分析其前后端交互的时候,curl的威力便显现出来。

1) 抓取“zhangxiaoyang.me”可以这样做:

$ curl zhangxiaoyang.me

然后会将“zhangxiaoyang.me”对应的页面抓取并打印。

2) 如果我们想将其输出到文件中,可以这样:

$ curl -o homepage.txt zhangxiaoyang.me

3) 以上命令会输出进度条,关闭进度条:

$ curl -s -o homepage.txt zhangxiaoyang.me

4) 如果不幸网络断开或出现了异常情况导致中途下载失败了,不要怕,这在下载大文件的时候尤为重要:

$ curl -c zhangxiaoyang.me

5) 查看Response:

$ curl -i zhangxiaoyang.me

会得到以下输出:

HTTP/1.1 200 OK
Server: GitHub.com
Content-Type: text/html; charset=utf-8
Last-Modified: Fri, 10 Jun 2016 08:57:31 GMT
Access-Control-Allow-Origin: *
Expires: Wed, 15 Jun 2016 09:47:25 GMT
Cache-Control: max-age=600
X-GitHub-Request-Id: 67F5E014:77FC:607C940:576121D5
Content-Length: 69798
Accept-Ranges: bytes
Date: Wed, 15 Jun 2016 10:32:46 GMT
Via: 1.1 varnish
Age: 304
Connection: keep-alive
X-Served-By: cache-itm7425-ITM
X-Cache: HIT
X-Cache-Hits: 1
Vary: Accept-Encoding
X-Fastly-Request-ID: 451b9e855cc2435b5898ab8dea776a37d2c2688a

<!doctype html>
<html>
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="description" content="爱美工的程序员"/>
    ...

其中,“200 OK”为HTTP状态码,表示请求和响应都是OK的。状态码可以分为1XX、2XX、3XX、4XX、5XX五类:

  • 1XX为信息性状态码,表示接收的请求正在处理;
  • 2XX为成功状态码,表示请求正常处理完毕;
  • 3XX为重定向状态码,表示需要进行附加操作以完成请求;
  • 4XX为客户端错误状态码,表示服务器无法处理请求;
  • 5XX为服务器错误状态码,表示服务器处理请求出错。

6) 以上会输出Reponse首部和网页源代码,如果只想查看Response的首部:

$ curl -I zhangxiaoyang.me

7) 同时查看Request和Response:

$ curl -v zhangxiaoyang.me

8) 把自己伪装成浏览器吧:

$ curl -A "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36" zhangxiaoyang.me

2.2 curl知新

1) 由于种种原因,有些URL不能一次抓取成功,可能需要多试几次(最多尝试3次):

$ curl --tries 3 zhangxiaoyang.me

2) 设置连接超时(3秒):

$ curl --connect-timeout 3 google.com

3) 同时抓取多个URL:

$ curl URL1 URL2 ...

4) 抓取一个HTTP或FTP协议的URL(URL前缀分别是http://ftp://),但是需要验证账号和密码:

$ curl -u USERNAME:PASSWORD URL

5) 处理自动跳转的网站:

$ curl -I -L www.sina.com
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Wed, 15 Jun 2016 12:09:52 GMT
Content-Type: text/html
Location: http://www.sina.com.cn/
Expires: Wed, 15 Jun 2016 12:11:52 GMT
Cache-Control: max-age=120
Age: 70
Content-Length: 178
X-Cache: HIT from xd33-90.sina.com.cn

HTTP/1.1 200 OK
Content-Type: text/html
Vary: Accept-Encoding
X-Powered-By: shci_v1.03
Server: nginx
Date: Wed, 15 Jun 2016 12:10:54 GMT
Last-Modified: Wed, 15 Jun 2016 12:09:08 GMT
Expires: Wed, 15 Jun 2016 12:11:54 GMT
Cache-Control: max-age=60
Age: 9
Content-Length: 576807
X-Cache: HIT from xd33-93.sina.com.cn

如果不处理跳转,则:

$ curl -I www.sina.com
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Wed, 15 Jun 2016 12:10:10 GMT
Content-Type: text/html
Location: http://www.sina.com.cn/
Expires: Wed, 15 Jun 2016 12:12:10 GMT
Cache-Control: max-age=120
Age: 50
Content-Length: 178
X-Cache: HIT from xd33-91.sina.com.cn

6) 发一个GET请求(curl默认就是GET请求,所以无需加任何参数):

$ curl www.baidu.com/s?wd=%E5%9C%A8%E4%BD%A0%E8%BA%AB%E8%BE%B9%E4%B8%BA%E4%BD%A0%E8%AE%BE%E8%AE%A1

7) 发一个POST请求到URL:

$ curl -d 'user=yang%20zhang&password=test' URL
OR
$ curl --data 'user=yang%20zhang&password=test' URL

8) 发一个POST请求到URL,并自动进行urlencode:

$ curl --data-urlencode 'user=yang zhang&password=test' URL

9) 自定义请求URL时使用的方法:

$ curl -X GET URL
$ curl -X POST URL
$ curl -X PUT URL
$ curl -X DELETE URL

10) 指定referer字段,标识我们是从哪个URL跳转过来的(网站可以以此来判别我们有没有盗链或者抓站):

$ curl -e http://zhangxiaoyang.me/index.html http://zhangxiaoyang.me/category.html

11) 附加一个自定义的首部字段,比如:

$ curl -H 'Content-Type:application/json' URL

12) 访问HTTPS协议的网站时指定本地证书文件LOCAL_PEM:

$ curl -E LOCAL_PEM URL

2.3 curl上传与下载

1) 使用PUT方法将本地文件LOCAL_FILE上传至URL:

$ curl -T LOCAL_FILE URL

2) 通过表单上传本地文件LOCAL_FILE:

假设form表单如下:

<form method="POST" enctype="multipart/form-data" action="upload.cgi">
  <input type="file" name="upload">
  <input type="submit" name="press" value="OK">
</form>

则:

$ curl -F upload=LOCAL_FILE --form press=OK URL

3) 我们已经get的-o选项,可以用于下载一个文件:

$ curl -o spider-logo.png http://zhangxiaoyang.me/drafts/categories/weixin/images/spider-logo.png

4) 简化以上写法:

$ curl -O http://zhangxiaoyang.me/drafts/categories/weixin/images/spider-logo.png

5) 根据规则实现批量下载(抓取spider-logo.png和spider-wget.png):

$ curl -O 'http://zhangxiaoyang.me/drafts/categories/weixin/images/spider-{logo,wget}.png'

6) 抓取URL/a/1.jpg~URL/a/100.jpg和URL/b/1.jpg~URL/b/100.jpg共计200张图片:

$ curl -O 'URL/{a,b}/[1-100].jpg'

注意:由于文件名重复,使得后面下载的100图片会覆盖前面下载的。

7) 解决上面的问题:

$ curl #1_#2.jpg 'URL/{a,b}/[1-100].jpg'

图片会存成a_1.jpga_2.jpg这样的格式,#1表示URL中出现的第一个变量。

1) 自行指定请求URL时附带的Cookie值:

$ curl --cookie "name=xxx" URL

2) Cookie往往来源于上一次的Response,我们可以把Cookie存到本地:

$ curl -c LOCAL_COOKIE URL

3) 下一次访问便可以使用该Cookie文件了,更加方便:

$ curl -b LOCAL_COOKIE URL

2.5 curl与wget的对话

curl:你都会啥绝活?

wget:我会抓站。

curl:我支持的协议比你多呢!

wget:我会抓站。

curl:我调试服务器方便。

wget:我会抓站。

curl:我爸是libcurl

wget:我会抓站。

...

参考