selenium + requests 爬取知乎美女头像

浏览 421

课文

从知乎的哪个页面能源源不断地获取到用户头像呢? 试想我们刷推荐页时是不是可以一直能刷到新回答,通过这些回答我们就能源源不断地获取到用户头像。对于知乎,同样是需要我们登录才能进行操作,我们打开知乎的推荐页面 [https://www.zhihu.com/index](https://www.zhihu.com/index),如果没登录的情况下会跳到登录页面。 ![image](https://qiniu.3yya.com/5c35983da4b63ca66a00ca239185a36c/5c35983da4b63ca66a00ca239185a36c.png) 对于需要爬取登录的页面一般有三个处理方法。 | 方法 | 优势|劣势 | | --- | --- | --- | | 分析 http 请求,使用 requests 模拟登录,获取 cookies 之后模拟登录。 | 速度快 | 分析请求难度高,难以处理验证码登录。 | | 使用 selenium 调用浏览器,手动登录之后爬取数据。 | 操作简单,可以手动处理验证码。 | 速度慢 | | 使用 selenium 调用浏览器手动登录,保存 cookies,用 requests 携带 cookies 爬取数据。 | 速度快,操作简便 | 有些情况不适用 | 对于花瓣网我们采用的是第二种方法,全程用 selenimu 处理登录与数据的爬取。对于知乎我们尝试第三种方法,就是 selenimu 手动登录后获取带登录信息的 cookies,再使用 requests执行数据的爬取。 ## selenimu 登录知乎 二话不说先上 selenimu 。 ```python from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.zhihu.com/signin') ``` 可是在我们登录时,会发现登录不上。 ![image](https://qiniu.3yya.com/0b4dfd804e1f12fba68deba1fac7045a/0b4dfd804e1f12fba68deba1fac7045a.png) 这是因为知乎通过检测浏览器的特征,发现了我们是使用的 selenium 运行的浏览器进行的登录,因此拒绝了我们的登录,这是知乎的反爬机制之一。 那我们有什么应对方法吗? 我们可以通过用正常的浏览器来打开知乎,用 selenium 连接正常的浏览器进行调试,这样就可以绕开知乎的反爬机制了。 ```python import os from selenium.webdriver.chrome.options import Options from selenium import webdriver os.system('start Chrome --remote-debugging-port=9222') options = Options() options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") browser = webdriver.Chrome(options=options) browser.get('https://www.zhihu.com/signin') ``` 如果你出现下图的提示,可以尝试在 os.system 中把 Chrome 替换成绝对路径,或者最好按照上章节的内容,将 Chrome 所在路径加入 windows 的 PATH 变量。 ![image](https://qiniu.3yya.com/a5b4019c650f1763e4f6cd0a47a2480e/a5b4019c650f1763e4f6cd0a47a2480e.png) ## 获取知乎推荐的接口 在登录后知乎推荐页面右键检查,打开 Network 标签,点击 XHR 过滤, 往下滚动页面,触发网页加载数据。这会我们能看到 Network 中会出现一些网络请求。 ![image](https://qiniu.3yya.com/7f75b91b689a21a4f1928d2dad70fcd0/7f75b91b689a21a4f1928d2dad70fcd0.png) 点击这些请求, 会发现 Name 为 batch 的请求并没有在 response 中返回数据, 而只有 recommend 请求中返回了数据。 ![image](https://qiniu.3yya.com/b8ed7eb01667642aee9626b2e8e7f729/b8ed7eb01667642aee9626b2e8e7f729.png) ![image](https://qiniu.3yya.com/c64484333b9cb35b5e3403360fba574f/c64484333b9cb35b5e3403360fba574f.png) 将 recommend 的请求链接复制到浏览器打开,可以看到返回了数据, 可以断定 recommend 这个请求正是知乎用来加载数据的请求。 ![image](https://qiniu.3yya.com/fe30e55391aed22c8fac1dff9d6f584d/fe30e55391aed22c8fac1dff9d6f584d.png) 如果我们把这个请求链接复制到一个未登录知乎的浏览器中,比如打开一个新的无痕浏览器。 ![image](https://qiniu.3yya.com/69797040ed697b4a44673cd0eaa86fbf/69797040ed697b4a44673cd0eaa86fbf.png) 会发现同样的一个请求链接却无法返回数据,这是因为登录信息是保存在 cookie 当中,而打开的无痕浏览器由于并未登录知乎,所以没有登录信息,导致无法获取到数据。 ![image](https://qiniu.3yya.com/5b3c11f781693e4c35eb005db95d7609/5b3c11f781693e4c35eb005db95d7609.png) ## 分析接口中参数的含义 让我们多滚动下页面,接着从 `Network`中找两个紧邻的接口调用的请求来对比下。 ![image](https://qiniu.3yya.com/0b66d4e228f3eb671cd8045f7f2d1534/0b66d4e228f3eb671cd8045f7f2d1534.png) 接口 1: [`https://www.zhihu.com/api/v3/feed/topstory/recommend?session_token=752384e9047dd3af1f43ed7ca68e487c&desktop=true&page_number=3&limit=6&action=down&after_id=11&ad_interval=-1`](https://www.zhihu.com/api/v3/feed/topstory/recommend?session_token=752384e9047dd3af1f43ed7ca68e487c&desktop=true&page_number=3&limit=6&action=down&after_id=11&ad_interval=-1) 接口 2: [`https://www.zhihu.com/api/v3/feed/topstory/recommend?session_token=752384e9047dd3af1f43ed7ca68e487c&desktop=true&page_number=4&limit=6&action=down&after_id=17&ad_interval=-1`](https://www.zhihu.com/api/v3/feed/topstory/recommend?session_token=752384e9047dd3af1f43ed7ca68e487c&desktop=true&page_number=4&limit=6&action=down&after_id=17&ad_interval=-1) 很多同学都是看了几百集柯南过来的,让我们借用下柯南的推理能力。 `session_token` 应该是 session 的凭证,`page_number` 与 `after_id` 这两个比较像控制数据页数的参数,因为它们是唯一改变的量, `page_number` 是每次加 1, `after_id` 是每次加 6, 因为每页返回的数据条数是 6 条。 经进一步的测试, 我们发现把其他参数都去掉只保留 `page_number` 与 `after_id` 依旧是可以获取到数据的,这样子就不需要费劲去获取 `session_token` 的值了,它并不是一个必须的参数。 ![image](https://qiniu.3yya.com/5ca4e31dc1c9a8da02fd123996b89e4e/5ca4e31dc1c9a8da02fd123996b89e4e.png) 到目前为止,我们应该修改 `page_number` 与 `after_id` 来获取更多数据,但其实这两个参数也可以去掉,只保留 [`https://www.zhihu.com/api/v3/feed/topstory/recommend`](https://www.zhihu.com/api/v3/feed/topstory/recommend) 每次刷新返回的也是新数据。 这可能是知乎后端接口升级,不再需要这些参数了,但前端代码未更改,所以调用时仍然有这些参数的原因。 ## 用 fake_useragent 生成 User-Agent 之前的 User-Agent 都是从浏览器的请求中复制的,这次我们使用 fake_useragent 这个库来生成一个 User-Agent。 ```python pip install fake-useragent ``` `fake_useragent` 可以生成模拟一些浏览器的 User-Agent 信息,或者生成一个随机的 User-Agent 信息。 ```python from fake_useragent import UserAgent ua = UserAgent() print(ua.ie) print(ua.chrome) print(ua.firefox) print(ua.random) ``` ```python Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US) Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.36 Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.0 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36 ``` ## 用 requests 请求数据 我们先尝试着不带登录来获取数据,会发现是失败r ```python import requests from fake_useragent import UserAgent ua = UserAgent() response = requests.get( 'https://www.zhihu.com/api/v3/feed/topstory/recommend', headers={'User-Agent': ua.random}, ) print(response.status_code) print(response.text) ``` ```python 401 {"code":"100010","'message'":"Unsupported auth type oauth"} ``` 这是因为没有携带 cookie 中的登录信息,我们尝试着先在 selenium 中先手动完成登录,接着再使用 selenium 中的 cookies 完成信息的爬取。 在这之前我们先打印看一下 selenium 能获取到的 cookies 格式。 ```python import os from selenium.webdriver.chrome.options import Options from selenium import webdriver os.system('start Chrome --remote-debugging-port=9222') options = Options() options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") browser = webdriver.Chrome(options=options) browser.get('https://www.zhihu.com/signin') input('登录完成后回车:') print(browser.get_cookies()) ``` ```python [{'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'secure': False, 'value': 'd1f07ca9b929274b65d830a00cbd719a|1621633798|1621633790'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'secure': False, 'value': '1621633798'}, {'domain': 'www.zhihu.com', 'expiry': 2147483647, 'httpOnly': False, 'name': 'YD00517437729195%3AWM_NIKE', 'path': '/', 'secure': False, 'value': '9ca17ae2e6ffcda170e2e6eeadc25df8bdfa8bb56e87868ab6d44a839a9b84ae61fcbc9e97c621b6e9b985e22af0fea7c3b92ab6e7a78cd95fac93a2b9f96bb788a396c472f8e9a5a9b77df7b3a98ee95bf3948ba5b446b5e8bad3e721b49aa9b8ce67b5e88ebbf44fb7b786a7dc3b85bf9683aa54afb48eb3e762b48aa290fb65b09dffccb35cbbb4a0b8db5cad8789bac96bf4babd95c6339b99aa8bfc69e9ed96aacf7395ad98a5ec3bf2ba8fd8aa4093ee9ed1c837e2a3'}, {'domain': ...... ``` selenium 的 get_cookies 方法返回的是一个数组,然而 requests 中的 cookies 需要的是一个字典,应该怎么处理呢? 我们发现 get_cookies 返回的数组中每一个元素都是有相同的键的字典, `domain` 是它的域名,`name` 与 `value` 是 cookies 中的名称与值, 对应到 requests 的 cookies 中正是字典的键与值。 我们用一行代码将其做一个转换。 ```python cookies = {item['name']: item['value'] for item in browser.get_cookies()} print(cookies) ``` ```python {'SESSIONID': '3gEgBHCHZWUqgj3736824OvBHdnuVoHGyjekfKytnWr', 'KLBRSID': '57358d62405ef24305120316801fd92a|1621634134|1621634130', 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49': '1621634133', 'osd': 'VFgXAkn81uzZjbsSVfuUs40d24JAybOqvcv1Ux6ih7ur6dUnYK77RL6PvxNVnwO7ErO5iH1fYT2RaTkXjvqpZAM=', 'YD00517437729195%3AWM_NIKE': '9ca17ae2e6ffcda170e2e6eeadc25df8bdfa8bb56e87868ab6d44a839a9b84ae61fcbc9e97c621b6e9b985e22af0fea7c3b92ab6e7a78cd95fac93a2b9f96bb788a396c472f8e9a5a9b77df7b3a98ee95bf3948ba5b446b5e8bad3e721b49aa9b8ce67b5e88ebbf44fb7b786a7dc3b85bf9683aa54afb48eb3e762b48aa290fb65b09dffccb35cbbb4a0b8db5cad8789bac96bf4babd95c6339b99aa8bfc69e9ed96aacf7395ad98a5ec3bf2ba8fd8aa4093ee9ed1c837e2a3', ...... ``` 成功转换成了 requests 所需的字典的格式,一大串的数据看着恐怖,我们也无需分析其中每个键表示的什么信息,只需知道其中包含着我们的登录信息,之后只需一股脑地都带上就行。 ```python import os import requests from fake_useragent import UserAgent from selenium.webdriver.chrome.options import Options from selenium import webdriver os.system('start Chrome --remote-debugging-port=9222') options = Options() options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") browser = webdriver.Chrome(options=options) browser.get('https://www.zhihu.com/signin') input('登录完成后回车:') cookies = {item['name']: item['value'] for item in browser.get_cookies()} ua = UserAgent() response = requests.get( 'https://www.zhihu.com/api/v3/feed/topstory/recommend?page_number=4&after_id=17', headers={'User-Agent': ua.random}, cookies=cookies, ) print(response.status_code) print(response.text) ``` ```python 登录完成后回车: 200 ``` ![image](https://qiniu.3yya.com/e8dbe4ca3ed66b4049568a196907b500/e8dbe4ca3ed66b4049568a196907b500.png) ## 保存用户头像 我们已经能从接口获取到数据了,接下来就是把用户的头像保存到本地。回到网页,我们来分析一下接口返回的数据。 每次返回的数据总共有 6 条, 会存放在 `data` 这个元素中。 ![image](https://qiniu.3yya.com/0e0afd8f9a354ce6c47ab6db470b03a4/0e0afd8f9a354ce6c47ab6db470b03a4.png) 展开 `data` 中的第一个元素, 我们能看到作者的数据存在于 `target` 元素下的 `author` 元素当中, 但是经我的对比,这里数据中的 `gender` 在男性用户时为 `1` ,在女性或未标明性别时都为 `0` , 这对我们筛选帅哥美女是不利的,但我们可以通过 `url` 这个数据链接进一步获取详细的用户信息。 ![image](https://qiniu.3yya.com/75aff2e1ab6b097a0c5efc43ca9fdc7b/75aff2e1ab6b097a0c5efc43ca9fdc7b.png) 打开 `url`后能获取到详细的用户信息, `name` 表示用户名, `avatar_url` 表示头像链接, `gender` 表示性别, `0` 为女性、 `1` 为男性、 `-1` 为未标明性别,关于 `gender` 的含义可以通过多对比几个用户信息得出。 ![image](https://qiniu.3yya.com/268f562f4e342478757487f95b824417/268f562f4e342478757487f95b824417.png) 我们还需要过滤掉机构号与匿名用户, `type` 为 `people` 时代表为个人用户,当为匿名用户时, `url` 元素为空字符串。 ![image](https://qiniu.3yya.com/1d75a52c7f78d6411296362cfbf60f4d/1d75a52c7f78d6411296362cfbf60f4d.png) 还有一点就行,对于获取到的头像链接,比如: [`https://pic3.zhimg.com/v2-d24d80fc29a1b117cf7a7fe027f6d3b5_l.jpg`](https://pic3.zhimg.com/v2-d24d80fc29a1b117cf7a7fe027f6d3b5_l.jpg) , 直接打开会是一个小图, ![image](https://qiniu.3yya.com/716ecfc5ac99db429f1ee051b872435c/716ecfc5ac99db429f1ee051b872435c.png) 但如果我们把链接中的 `_l` 去掉,就能获取到一张原图。 ![image](https://qiniu.3yya.com/24382029ec91c1b214795cf7b060066b/24382029ec91c1b214795cf7b060066b.png) ## 最终代码 ```python import os import requests from fake_useragent import UserAgent from selenium.webdriver.chrome.options import Options from selenium import webdriver os.system('start Chrome --remote-debugging-port=9222') options = Options() options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") browser = webdriver.Chrome(options=options) browser.get('https://www.zhihu.com/signin') input('登录完成后回车,如果已是登录状态直接回车:') cookies = {item['name']: item['value'] for item in browser.get_cookies()} ua = UserAgent() # 图片保存路径 path = '知乎头像' # 0 为女性用户 # 1 为男性用户 # -1 为未标注性别 # 这里爬取美女的头像 gender = 0 # 判断目录是否存在,不存在则创建 if not os.path.exists(path): os.makedirs(path) # 爬取 20 x 6 = 60 条数据 for _ in range(20): response = requests.get( 'https://www.zhihu.com/api/v3/feed/topstory/recommend', headers={'User-Agent': ua.chrome}, cookies=cookies, ) if response.status_code != 200: print('获取数据出错') continue for item_data in response.json()['data']: user_data = item_data['target']['author'] if user_data['type'] != 'people': print('非用户类型') continue if user_data['url'] == '': print('匿名用户') continue response = requests.get( user_data['url'], headers={'User-Agent': ua.random}, cookies=cookies, ) if response.status_code != 200: print('获取数据出错') continue detail_data = response.json() if detail_data['gender'] != gender: continue print(f'保存{detail_data["name"]}的头像中...') # 获取原图 avatar_url = detail_data['avatar_url'].replace('_l', '') image_response = requests.get(avatar_url) if image_response.status_code != 200: print('获取数据出错') continue with open(os.path.join(path, detail_data['name'] + '.jpg'), mode='wb') as f: f.write(image_response.content) ``` ```python 登录完成后回车,如果已是登录状态直接回车: 匿名用户 保存闲杂人士的头像中... 保存吃货一枚的头像中... 保存齐木楠子的头像中... 保存青雀的头像中... 匿名用户 保存叮咚在云上的头像中... 获取数据出错 保存啊落英缤纷的头像中... ...... ``` ![image](https://qiniu.3yya.com/c27fbf3cc31d7d66caff788b4104943a/c27fbf3cc31d7d66caff788b4104943a.png)

评论

登录参与讨论

暂无评论

共 0 条
  • 1
前往
  • 1