#老司机教你看妹子——你的第一个知乎爬虫(2)

老司机教你看妹子——你的第一个知乎爬虫(2)

“老师快教我怎么才能获取完整图片?”

别急,还记得上回说的,设置UA的事情么

“嗯, 要把自己伪装成一个浏览器

我们先看看浏览器做了什么。

浏览器做了什么

你已经知道了,知乎的网页是通过 Ajax 进行 异步加载, 证据就是那个大大的“更多”按钮。

点一下,就加载更多。

那么点了按钮之后究竟发生了什么呢?浏览器可以告诉我们究竟发生了什么。

使用Chrome浏览器,在页面上单击鼠标右键->审查元素(检查),就可以唤起开发者工具。

切换到Network一栏里,然后点击“更多”,就可以看到点击了“更多”后面发生了什么网络请求。

你看到了什么?

“一堆图片请求,然后上面的那个看图标应该是文件之类的,而且有29k,加载了630ms。而且,从时间顺序上看,先执行获取到了这个数据,然后再加载的图片。”

Ok,那我们点开看一下。

fetch-preview.png
fetch-preview2.png

啊哈!

“这里是具体的回答数据!那我只需要调用这个接口,就可以拿到所有的答案了!“

Copy as cURL

你迫不及待的写下了,r = requests.get(url,headers=ua_header) 这样的代码,却发现了我在旁边笑而不语。

“有什么问题么?”

不要急着动手写,先分析下这个请求都带了那些参数。

“是指的url里的参数么?”

不是。选中那个请求,然后右键->Copy->Copy as cURL

cURL是一个利用URL语法在命令行下工作的文件传输工具,绝大多数的linux 和unix都会内置的一个软件.
但是,注意windows下命令行里调用的curl并不是这里提到的那个curl,具体的讨论可以看这篇:PowerShell 为什么 alias 了 curl 就引起了如此大的争议?

“那我现在要去装curl?”

别急啊,我们先看看我们复制出来了什么。

curl 'https://www.zhihu.com/api/v4/questions/22212644/answers?sort_by=default&include=data%5B%2A%5D.is_normal%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Cmark_infos%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%2Cupvoted_followees%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=721&offset=0' -H 'Cookie: aliyungf_tc=AQAAADvok2VGQgEAiE1NfJc49LzrUk7p; q_c1=ef155c9cc9124e1f951447b3b6fd875f|1500990779000|1500990779000; _zap=84936da1-3813-4842-a846-56214e791bd0; q_c1=70bd5b2623c14a3d9079630a483b0aa3|1500990778000|1500990778000; l_n_c=1; l_cap_id="NDczNThhZjExZWU1NDIzYWJhMDllNjBkZTJiNDUwZGI=|1500990807|075d561c18784c75946ae101e6f7135b5b271cf3"; r_cap_id="Y2MwNzdlMjMxMjc0NDViMGE0NWJhOTI3NGQzNjQwNGY=|1500990807|769b04b121d8b52602f6d7f3e86ca62bf5f80d33"; cap_id="ZTFkYmRiMjZiZTgyNDdjY2I3YTk3ZDExNDZjZDUyYmY=|1500990807|2f85fb4342ee06cfe26e5ba006d699c778a7787c"; n_c=1; _xsrf=b967f17b-4577-428e-906d-1c306c67decd' -H 'Accept-Encoding: gzip, deflate, br' -H 'Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.6,en;q=0.4' -H 'authorization: oauth c3cef7c66a1843f8b3a9e6a1e3160e20' -H 'accept: application/json, text/plain, */*' -H 'Referer: https://www.zhihu.com/question/22212644' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36' -H 'Connection: keep-alive' --compressed

嗯,这么长一行确实挺恶心的,我们来换个行。

curl
'https://www.zhihu.com/api/v4/questions/22212644/answers
?sort_by=default
&include=data%5B%2A%5D.is_normal%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Cmark_infos%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%2Cupvoted_followees%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics
&limit=20
&offset=3'
-H 
'Cookie: 
    aliyungf_tc=AQAAADvok2VGQgEAiE1NfJc49LzrUk7p;   q_c1=ef155c9cc9124e1f951447b3b6fd875f|1500990779000|1500990779000; _zap=84936da1-3813-4842-a846-56214e791bd0;
    l_n_c=1;
    l_cap_id="NDczNThhZjExZWU1NDIzYWJhMDllNjBkZTJiNDUwZGI=|1500990807|075d561c18784c75946ae101e6f7135b5b271cf3";
    r_cap_id="Y2MwNzdlMjMxMjc0NDViMGE0NWJhOTI3NGQzNjQwNGY=|1500990807|769b04b121d8b52602f6d7f3e86ca62bf5f80d33";    cap_id="ZTFkYmRiMjZiZTgyNDdjY2I3YTk3ZDExNDZjZDUyYmY=|1500990807|2f85fb4342ee06cfe26e5ba006d699c778a7787c";
    n_c=1;
    _xsrf=b967f17b-4577-428e-906d-1c306c67decd'
-H 'Accept-Encoding: gzip, deflate, br'
-H 'Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.6,en;q=0.4'
-H 'authorization: oauth c3cef7c66a1843f8b3a9e6a1e3160e20'
-H 'accept: application/json, text/plain, */*'
-H 'Referer: https://www.zhihu.com/question/22212644'
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
-H 'Connection: keep-alive'


curl的第一参数就是要请求的url, -H表示一个Header,所以,这个请求总共有8个Header,其中一个是cookie。

“老师,cookie是啥”

(如果你知道cookie是啥,可以跳过下面的小节)

cookie与session

我们从一个故事开始。

你去柜台买东西,付钱拿货走人。第二天你又去柜台,说说好的赠品还没给呢。店员说,你谁啊,要啥赠品啊,买东西那边排队去。卒。后来为了避免这种情况,柜台会给个你一个收据,同时也会记录那些东西卖出去了,还欠着赠品。你下次来的时候,带着收据,柜台根据收据,一查账,诶发现确实欠你个赠品,然后给你赠品。大家都美滋滋。

“店员就这么蠢,一点都不记事么”

对,最开始的http协议就是这样,一次请求结束后,连接就断开了。后来有了cookie的诞生,发送一些额外的信息,用来标记用户。

“那cookie就是收据,session就是店家的记录?”

对,cookie和session分别表示存在浏览器和服务器的某些数据,这些数据被用来标识用户和记录状态。而且cookie是通常都是服务器设置的。

Request with cookie

“那我就把这些cookie直接写近代码里就可以了么?”

cookie会过期,所以,最好还是要知道这些cookie从何而来。

我们从头开始,看看服务器给我们设置了那些cookie

在做这个之前,你要先进入Chrome的隐身模式,隐身模式会清除你以前浏览器留下的cookie,其实之前我们所有的请求都是在隐身模式下发起的。

点开第一条请求,我们看到了_xsrf和aliyungf_tc这两个cookie

_xsrf这样的字段通常是用来防止XSRF(跨站请求伪造)的,而且aliyungf_tc,从名称上来看,应该是阿里云高防塞的。

“那q_c1, l_n_c, l_cap_id,r_cap_id,cap_id, n_c这些呢?”

一点小经验:这种充斥着大量连字符、意义不明的cookie,都可以忽略,这些通常是在某些中间步骤被设置进去的。

我们分析了这么多,终于可以开始写了。

我们上次用到的requests库,提供了一个requests.Session()对象,可以用来很方面的模拟一次会话,它会设置根据服务器的返回自动cookie。

def init(url):
    ua = {'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'}
    s = requests.Session()
    s.headers.update(ua)
    ret=s.get(url)
    return s

“老师,这个get的这个url是干啥用的,为啥返回值直接不要了”

会想一下我们刚刚分析的整个流程,首先打开了某个网页,然后JS调用了"https://www.zhihu.com/api/v4/questions/22212644/answers"这样一个接口。所以,我们要模拟打开网页的那一步,让服务器给我们设置cookie。

“然后接下来模拟js调用?”

然也

def fetch_answer(s,qid,limit,offset):
    params={
        'sort_by':'default',
        'include':'data[*].is_normal,is_collapsed,annotation_action,annotation_detail,collapse_reason,is_sticky,collapsed_by,suggest_edit,comment_count,can_comment,content,editable_content,voteup_count,reshipment_settings,comment_permission,mark_infos,created_time,updated_time,review_info,relationship.is_authorized,is_author,voting,is_thanked,is_nothelp,upvoted_followees;data[*].author.follower_count,badge[?(type=best_answerer)].topics',
        'limit':limit,
        'offset':offset
    }
    url ="https://www.zhihu.com/api/v4/questions/"+qid+"/answers"
    return s.get(url,params=params)

qid,就是url后面的那一串数字,question_id,limit表示一次拉去多少个回答、offset表示从第几个开始。

接下来你就可以运行一下了。

url = "https://www.zhihu.com/question/29814297"
session = init(url)
q_id = url.split('/')[-1]
offset = 0
limit=20
ret=fetch_answer(session,q_id,limit,offset)

如果不出意外的话,你可能会看到这样一个结果:

“呃,老师这个\u8bf7这一串是什么”

最简单的方式,把这一串copy出来,贴到交互式窗口里:

请求头错误,我们可以通过ret.request.headers查看我们个发出的请求,究竟包含了那些header

对比之后,发现,少了authorization: oauth c3cef7c66a1843f8b3a9e6a1e3160e20

我们在init函数里把它加上

def init(url):
    ua = {'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'}
    s = requests.Session()
    s.headers.update(ua)
    ret=s.get(url)
    s.headers.update({"authorization":"oauth c3cef7c66a1843f8b3a9e6a1e3160e20"})
    return s

在执行一次,我们终于成功了

获取所有答案

“等一下,老师,刚刚那个fetch_answer,limit我们可以直接设置到99999啊,然后不久可以一口气拿回来所有的么,或者我们找下总共有多少个回答,然后设置成那个数字不就好了嘛。”

你一天吃三顿饭,但是你会一口气吃光三顿饭么!这么做当然可以,但是这样的行为十分反常,而且很可能触发某些反爬虫机制,因为这个特征太明显了。牢记一点,你是一个浏览器。

(实际上知乎会把大于20的limit当做20去处理)

这里我们因为不关心总数,所以就利用fetch_answer返回的paging里的is_end字段,来判断是否获取完毕。

def fetch_all_answers(url):
    session = init(url)
    q_id = url.split('/')[-1]
    offset = 0
    limit=20
    answers=[]
    is_end=False
    while not is_end:
        ret=fetch_answer(session,q_id,limit,offset)
        #total = ret.json()['paging']['totals']
        answers+=ret.json()['data']
        is_end= ret.json()['paging']['is_end']
        print("Offset: ",offset)
        print("is_end: ",is_end)
        offset+=limit
    return answers

最后,我们拿到了一个answer的数组,从每个answer里的'content'字段里找出url,下载就好了。

url = "https://www.zhihu.com/question/29814297"
answers=fetch_all_answers(url)
folder = '29814297'
for ans in answers:
    imgs = grep_image_urls(ans['content'])
    for url in imgs:
        download(folder,url)

完整的代码在这里

“老师图呢?”

最后爬取了“日常穿JK制服是怎样一种体验?”这样一个问题,拿到了970张图


“老师,我们下节课干啥”

关注我,下节课你就知道了。

我的知乎,或者我的简书

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容