爬虫实战3:模拟登陆知乎并爬取任意帖子数据

​  
   刚学爬虫时,看到一篇文章硬核破解知乎登陆,心潮澎湃,真男人!符合我的胃口!哼哧哼哧的立刻安排!

  (半个小时过后)

  似乎我是弱智?很多看不懂?(百度乱搜中…)恩还有种简单的?selenium+xpath模拟登陆?简单但效率低?难道要靠用这种低效率方式来爬取数据,这是向网站妥协!不,我常威就算没技术也绝对不要这样委屈求全!(十分钟过后)挨,怎么他喵这么香?

   经过一番曲折的故事(中间花了半个月拿了几个小项目练练手),终于进入激动人心的实战,马上就可以 爬取知乎姑娘的美照 学到新知识了!本文行文结构如下:

   1. 做好准备了吗

   2. selenium+xpath模拟登陆知乎获取cookies

   3. request+re+json+jsonpath 爬取知乎指定问题下所有回答数据(照片)

   4. 进行面对对象重新构造

   5. 总结

0、 做好准备了吗

  • selenium 安装配置教程

    selenium及相应环境驱动配置安装,请查看:

        selenium及驱动器安装配置教程详解

  • 什么是selenium

       不知道大家有没有用过按键精灵的API,用这套API我们就可以进行点击、输入等一系列模拟真实用户操作。selenium和其类似,只不过它可直接作用于浏览器,模拟打开、进行点击,输入等操作。用它可以很方便避开反爬机制,拿到我们要的界面源码进而获取数据,但是缺点速度太慢。

    ​ 了解更多:

        Python爬虫利器五之Selenium的用法

        Python 爬虫基础Selenium库的使用

  • 什么是xpath

       前面我们介绍过正则表达式和相应re模块,在网页上定位元素,获取数据re模块当然也可以做到但是过于繁琐。

​  ​  ​   XPath 是一门在 XML 文档中查找信息的语言,用来在网页XML 文档中对元素和属性进行遍历,语法简单方便。

 安装及了解更多:

    Python爬虫利器三之Xpath语法与lxml库的用法

  • json与jsonpath

       json(JavaScript Object Notation)是一种轻量级的数据交换格式,具有数据格式简单,读写方便易懂等优点,相对于XML来说,更加的轻量级,更方便解析,因此许多开发者都遵循json格式来进行数据的传输和交换。比如我们后面获取到的cookie和利用知乎API获取的回答信息都是json格式。

        jsonpath之于json 便如xptah之于XML,是专门用来解析json数据格式的模块,语法简洁比直接调用re模块正则解析方便。

     安装及了解更多:

        JSON入门教程

        json解析神器 jsonpath的使用

一、模拟登陆知乎


正如前言,模拟登陆知乎,有两种思路:

  • request硬核破解:

    知乎对Form Date数据进行了加密,要抓包分析同时对JavaScript有一定了解才能尝试分析出来。
    可参阅:模拟登陆知乎

  • selenium模拟登陆:

    模拟真实打开浏览器登陆,拿到cookies,再用request爬取数据,难度较低,可操作性强。

    这里我们采用第二种方式。

1.1 开始模拟登陆1:QQ模拟登陆


   模拟登陆有多种方式,使用知乎登陆界面提供的QQ号登陆可以避免验证码,进而获取cookies。另外一种便是使用手机号登陆,需要验证码,将在1.2 详细讲述。

  • 引入模块

    from selenium import webdriver  
    from base64 import b64decode   # 解码验证码b64编码,QQ登陆不用
    import time  # 用于暂停,防止被检测
    import json  # 保存json格式cookies,便于读取
    import os    # 主要用于创建文件夹等操作
    
  • 打开浏览器

    # 1.打开浏览器
    browser = webdriver.Chrome()
    browser.get('http://www.zhihu.com')
    

    执行这段代码,便会看到程序自动打开Chrome浏览器,进入到知乎登陆界面。

  • 找到QQ登陆按钮

    # 2.找到QQ登陆按钮
    # 先找到登陆按钮
    bt_opt_login = browser.find_element_by_xpath('//div[@class="SignContainer-switch"]/span')
    bt_opt_login .click()
    # 再选择社交账号方式登陆
    bt_opt_social = browser.find_element_by_xpath('//span[@class="Login-socialLogin"]/button')
    bt_opt_social.click()
    time.sleep(1)  # 等等QQ登陆图标加载一下
    # 最后点击QQ图标登陆
    bt_opt_QQ = browser.find_element_by_xpath('//span[@class="Login-socialButtonGroup"]/button[3]')
    bt_opt_QQ.click()
    time.sleep(1)  # 等待下加载完毕
    

    注意,最后一步选择QQ图标登陆按钮时,大家可以看到,其在一对<g></g>标签所中:
    在这里插入图片描述

    ​ 如果大家直接xpath定位到g标签中:

    //svg[@class="Icon Login-socialIcon Icon--qq"]/g
    

    ​ 是不能定位成功的,定位svg元素要用xpath的name()函数,且自svg以下都要用:*[name()='svg element']这种形式

    //span[@class="Login-socialButtonGroup"]/button[3]/*[name()='svg']/*[name()='g']
    

    ​ 也可以像上面,更简洁的直接定位到button[3]按钮标签即可(第三个是QQ登陆按钮)。

  • 切换到内置QQ登陆窗口登陆

    点击QQ图标选择登陆后,便会弹出一个内嵌QQ登陆子网页。自然而然,我们要切换窗口:

    # 3.操作刚打开的QQ登陆界面
    # 切换到qq登陆界面句柄
    allhandles = browser.window_handles
    zhihuHandle = browser.current_window_handle
    qqHandle = allhandles[1]  
    # qq登陆内嵌页面要切换表单
    browser.switch_to.frame("ptlogin_iframe")
    

        browser.window_handles 获取当前所有窗口句柄,第一个是知乎窗口,第二个自然是QQ登陆窗口。但是要提醒大家的是,QQ登陆是内嵌QQ登陆子网页,还需要切换表单frame,切换表单需要知道表单名,那么问题来了,如何找到当前QQ登陆子网页表单名?

       F12>>分析网页元素,ctrl+shift+f 打开搜索框,输入frame搜索,可以找到frame_name = ptlogin_iframe,复制进行切换表单。
    在这里插入图片描述

        接下来,就是模拟输入账号密码点击登陆了,距离成功登陆只差一点点啦!接下来的代码也清晰易懂,主要就是定位元素,这里xpath定位不太方便,直接查找id会更方便点。

    # 选择输入密码登陆
    bt_opt_inputAP = browser.find_element_by_xpath('//div[@id="bottom_qlogin"]')
    bt_opt_inputAP.click()
    # 开始模拟输入账号密码登陆
    text_qq_account = browser.find_element_by_id("u")
    text_qq_password = browser.find_element_by_id("p")
    bt_qq_login = browser.find_element_by_id("login_button")
    text_qq_account.send_keys("380141***")  # 输入你的QQ账号
    text_qq_password.send_keys("riguangyu******")  # 输入你的QQ密码
    bt_qq_login.click()
    time.sleep(3)
    

       最后大家不要忘了,切回知乎主窗口,表单不用切换。

    # 切回知乎窗口,表单不用
    browser.switch_to.window(zhihuHandle)
    

  • 保存cookie

      终于进入到知乎首页了,距离爬取好看的姑娘美照,啊不对更进一步学习更近了,哈哈~大家不要忘了我们辛苦模拟登陆就是为了拿到cookie,迫不及待来看看怎么保存cookie吧。

zhihu_cookies = browser.get_cookies()

    hh,获取cookie还是还简单的,返回的zhihu_cookies是一个字典类型。但是现在问题来了,如果我们直接将它写入txt文档,读取的时候是string类型,不再是字典,这样就很不方便读取里面数据了,总不然正则分析吧?

    这个时候json就闪亮登场了,我们用json.dumps将cookie转为json格式写入txt,读取的时候再用json.load方法读取,返回的就又是字典类型了。进一步了解,参照前: JSON入门教程

4.selenium保存cookie保存
zhihu_cookies = browser.get_cookies()
json_cookies = json.dumps(zhihu_cookies)
with open("C:\\Users\\86151\\Desktop\\json_cookies.txt", "w") as f:
    f.write(json_cookies)

    让我们看看txt里都写入了啥:
在这里插入图片描述

   嗷嗷,就是一个列表嘛,里面每一项又是字典。记住这个形式,方便后面我们理解读取cookie。

1.2 开始模拟登陆2:手机号模拟登陆


   前面虽然已经详细记录QQ模拟过程,但是我还是想记录下手机号登陆过程,因为这种登陆方式,必不可免的遇到处理验证码。如何处理验证码是学爬虫绕不过的坎,要撞的南墙。这里我将重点记录处理知乎验证的过程。如果迫不及待想看美羊羊洗澡,哦不小姐姐美照的同学可以略过这节。

  • 基本操作

    前面登陆过程和QQ登陆大同小异,这里直接给出代码供大家参考。

    from selenium import webdriver  
    from base64 import b64decode   # 解码验证码b64编码,QQ登陆不用
    import time  # 用于暂停,防止被检测
    import json  # 保存json格式cookies,便于读取
    import os    # 主要用于创建文件夹等操作
    
    
    count = 0
    while 1:
        # 1.打开浏览器
        # browser = webdriver.Chrome(chrome_options=chromeOptions)
        browser = webdriver.Chrome()
        browser.get('http://www.zhihu.com')
        
        # 2.输入账号密码
        # 1.点击登陆
        bt_opt_login = browser.find_element_by_xpath('//div[@class="SignContainer-switch"]/span')
        bt_opt_login .click()
        time.sleep(1)
        # 2.输入账号密码
        # elem_account = browser.find_element_by_name("username")
        # elem_pwd = browser.find_element_by_name("password")
        text_account = browser.find_element_by_xpath('//input[@name="username"]')
        text__pwd = browser.find_element_by_xpath('//input[@name="password"]')
        text_account.send_keys("1517948****")
        text__pwd.send_keys("riguangyu******")
    
        # 3.模拟点击登陆
        bt_login = browser.find_element_by_xpath('//button[@type="submit"]')
        bt_login.click()
    
    

        我们输入完手机号&密码点击登陆,本小节重点验证码boss便出现了,欲知我如何和验证码斗智斗勇大战三百回合且听下步分解。

  • 处理验证码

    ​ 知乎的验证码有两种,一种是还算人性化英文验证码:

    在这里插入图片描述

    一种便是毫无人性的中文验证码,要求我们点击图中倒立的中文字符:

    在这里插入图片描述

        中文验证码是比较难以处理的,我对它的处理方式就是:不处理~hh,简单来说,判断出现中文验证码就刷新界面,直至出现英文验证码开始输入验证码登陆。

       那么问题又来了(废宅就是问题多hh),如何判断出现中文字符?首先,直接定位中文验证码元素,如何定位不成功报错,说明出现的是英文验证码。捕获异常进行处理英文验证码即可。

    # 4.处理验证码登陆
    # 4.1 获取英文验证码图片
    time.sleep(1)  # 等待一会儿验证码图片还没加载
    # noinspection PyBroadException
    try:
        img_captcha = browser.find_element_by_class_name("Captcha-chineseImg")  # 定位到图片元素| "Captcha-englishImg"
        count += 1
        print("中文字符暂时不能处理,第{0}次重试...\n".format(count))
        browser.close()
        time.sleep(1)
        continue
    except Exception as e:
        img_captcha = browser.find_element_by_class_name("Captcha-englishImg")
        img64_src = img_captcha.get_attribute("src")  # 获取验证码b64编码
        img64_src = img64_src.replace("data:image/jpg;base64,", "")  # 删除前面标识
        img64_src = img64_src.replace("%0A", "\n")  # get_attribute会把\n替换成%OA,要替换回来
        img_date = b64decode(img64_src)
    

       大家可能会对验证码b64编码处理,这段代码有所疑问。

    1. 验证码是服务器自动生成的,不是给URL地址,而是一串b64编码字符,我们对它进行解码处理。可以看到下面这张图片,验证码b64编码见下:
      在这里插入图片描述

    2. 特别注意get_attribute这个方法会把\n替换成%OA,要替换回来

     处理好验证码,我们可以把它保存在本地,然后打开,这样我们只要手工输入验证码就可以登录了。

    # 4.2 保存图片
    img_path = captcha_savePath + "\\captcha.png"
    with open(img_path, "wb") as f:  # 将验证码图片以二进制流写入保存
        f.write(img_date)
    # 4.3 打开图片
    os.system(img_path)
    captcha = input("已捕捉到英文验证码,请在打开的图片识别输入:")
    time.sleep(2)
    # 4.4 输入验证码登陆
    elem_captcha_input = browser.find_element_by_xpath('//input[@name="captcha"]')
    elem_captcha_input.send_keys(captcha)
    # 4.5 登陆
    bt_login1 = browser.find_element_by_xpath('//button[@type="submit"]')
    bt_login1.click()
    time.sleep(2)
    

    在这里插入图片描述

    验证码处理完毕便可以登录到知乎首页了,同1.1接下来保存cookie即可。

  • 保存cookie

    # 5.保存cookie
    zhihu_cookies = browser.get_cookies()
    json_cookies = json.dumps(zhihu_cookies)
    with open("C:\\Users\\86151\\Desktop\\json_cookies.txt", "w") as f:
        f.write(json_cookies)
    

二、爬取知乎数据


   拿到cookie后我们又要掏出我们的老伙计------requests来爬取数据了。只要在requests请求时带上cookie参数,我们便可以相当于输入账号密码验证过身份直接登陆知乎了。

    我们表面随便(预谋已久)拿知乎某著名相亲贴爬取下面回答姑娘的照片来试试手:你的择偶标准是怎样的

  • 分析网站请求

    在这里插入图片描述

        我们可以看到,知乎回答只有往下滑动才能加载出更多。而知乎URL一直没有变化,想直接简单观察构造URL是行不通了。

       难道就这么放弃吗?我们爬虫,哦不,程序员绝不轻易认输!F12 分析网页元素,点击XHR窥屏网站请求(XHR类型即通过XMLHttpRequest方法发送的请求,知乎要请求加载回答)。然后,我们开始滑动,往下加载回答,看看都有什么请求。

       报告!发现可疑分子!收到~~让我们看看到底是何方神圣:
    在这里插入图片描述

       这个answer开头请求显然是非常可疑的,点进去窥视一下,发现有一大串的URL,复制过来我们把它粘贴过来打开浏览器访问(这里复制到firefox打开会自动转成json格式便于查看):
    在这里插入图片描述

       果不其然,这里就是请求的回答信息,服务器返回json格式。后来查阅了一下,没想到是知乎开放的API,我常威是靠某乎施舍…各位看官老爷,咋这就赶紧麻溜开始吧。上上张图片已经分析出下面这一大串URL规律:

    limt: 限制一次请求回答数,经过测试可以改为20,即一次请求20条回答。

    offset:偏移量,前面已有回答数。

    其余无变化。

        明白了知乎是怎么请求了,接下来只要构造request请求,会jsonpath对知乎API返回的json数据进行处理,就可以愉快的爬下我们想要的数据了(你明白的)。

  • 读取cookie构造request请求

       正如前cookie所说的格式,是一个列表,每一项是字典。每一个字典里有很多键值对,我们只需每个字典都有的键name&value对应的(其余的键值不用,这两个就够了),构造成一个新的 name值:value值对应的新cookie字典。

    # 5.读取cookies
    cookies_dic = {}
    with open("C:\\Users\\86151\\Desktop\\json_cookies.txt", "r") as f:
        cookies = json.load(f)
        # 在保存的cookies文档中,一个列表,每一项都是字典,字典又自然有多个键值对。只有每一个字典键值name,value对应的数据需要记住
        for cookie in cookies:
            cookies_dic[cookie['name']] = cookie['value']
    

        然后再传入headers伪装浏览器,循环构造URL请求进行请求即可。

    # 6.分析知乎帖子,正则匹配获取每篇答案的作者/赞同数/图片等信息
    desktop_path = "C:\\Users\\86151\\Desktop"
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"}
    answer_url = "https://www.zhihu.com/api/v4/questions/275359100/answers?include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%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%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%2Cis_labeled%2Cis_recognized%2Cpaid_info%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%2A%5D.topics&limit=20&offset={0}&platform=desktop&sort_by=default"
    # 爬十次,每次爬20篇回答,共200篇
    for i in range(10):
        dest_url = answer_url.format(i*20)
        response = requests.get(dest_url, headers=headers, cookies=cookies_dic)
    

       接下来,我们便开始用jsonpath解析获取的数据

  • jsonpath解析数据

        首先当然是用json.load加载读取到的数据,接着便是简单运用jsonpath对json数据进行解析:

        # 爬取的是json格式,要读取分析用json.loads载入
        json_date = json.loads(response.text)
        # 保存这20篇回答:作者 & 回答内容
        authors = jsonpath.jsonpath(json_date, '$..author.name')
        contents = jsonpath.jsonpath(json_date, '$..content')
    

        那么问题还是他喵又来了,回答里包含照片的URL地址信息,如何获得呢?jsonpath 和xpath肯定是不行的,什么你已经想到了?没错就是正则表达式(主要还是我睿智分析)!

       首先我们点开任意一个回答,看看照片深藏在哪个金屋中:
    在这里插入图片描述

        令人窒息的是,同一张照片URL既可能出现在<img src…标签中,也可能出现在data-original= …中,那么到底正则匹配哪个?经过一番尝试,如果匹配<img src…标签图片URL,会出现很多无关图片;匹配data-original= …中图片URL,不会出现无关照片,但是相邻两张图片URL是一样的需要去重。

    # 循环在指定root_path创建以作者名命名的文件夹,里面放有相应回答照片
        for j in range(20):  # 下载20页,每页20篇回答 
            # 创建以作者名命名的文件夹
            author_path = desktop_path+"\\知乎照片\\"+authors[j]
            if not os.path.exists(author_path):
                os.makedirs(author_path)
            # 用re正则解析对应的回答内容里面的图片
            img_pattern = re.compile('data-original="(.*?)"')
            img_urls = re.findall(img_pattern, contents[j])  # 相邻两个重复
            new_img_urls = []
            index = 0
            # 去除url相邻重复
            for item in img_urls:
                if index % 2 == 0:
                    new_img_urls.append(item)
                index += 1
    

        然后便是保存照片:

            # 判断是否有图片
            if len(new_img_urls) == 0:
                print("无照片")
                continue
            # 下载图片
            count = 0
            for url in new_img_urls:
                img_path = author_path + "\\" + str(count) + ".jpg"
                response_img = requests.get(url)
                with open(img_path, "wb") as f:
                    f.write(response_img.content)  # 注意要用response_img.content
                count += 1
            print("第{0}篇回答照片下载完毕".format(i*20+j+1))
        time.sleep(5)
    

       要注意的是,要用response_img.content(二进制格式)写入照片 而不是response_img.text(字符串)。

    ​ 到这里,我们等待下载完毕便可以啦!接下来,就是激动人心查看战果时间~

  • 查看下载结果

    在这里插入图片描述
    在这里插入图片描述

        这里我们发现了一点小瑕疵,有些图片下载失败,经过排查URL请求是正确的,单独下载也能成功,某些不可描述因素?后续如果解决,会进行更新~

       至此,知乎爬虫便基本制作成功了,接下来就是进行面对对象的重构,以及此次项目总结一些善后工作了。

三、面对对象重构

from selenium import webdriver
from base64 import b64decode
import requests
import re
import time
import json
import jsonpath
import os


class zhihuSpider:

    def __init__(self):
        # 初始化headers、登陆界面url、
        self.loginURL = "http://www.zhihu.com"
        self.headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"}

    def zhihu_qq_login(self, account, password):
        """
        :param account: 账号
        :param password: 密码
        :return: 尝试qq登陆,返回只含有键值name,value对应字典cookie
        """
        # 1.打开浏览器
        browser = webdriver.Chrome()
        browser.get(self.loginURL)

        # 2.查找登陆元素
        # 点击登陆
        # 因为'登陆'在一对<span>登陆</span>标签中,但是没有元素属性不好精确定位,采用XPATH语法
        bt_opt_login = browser.find_element_by_xpath('//div[@class="SignContainer-switch"]/span')
        bt_opt_login.click()
        # 选择QQ号登陆
        bt_opt_social = browser.find_element_by_xpath('//span[@class="Login-socialLogin"]/button')
        bt_opt_social.click()
        time.sleep(1)  # 等等QQ登陆图标加载一下
        bt_opt_QQ = browser.find_element_by_xpath('//span[@class="Login-socialButtonGroup"]/button[3]')
        bt_opt_QQ.click()

        # 3.操作刚打开的QQ登陆窗口
        time.sleep(1)  # 等待下加载完毕
        # 切换到qq登陆窗口
        allhandles = browser.window_handles
        zhihuHandle = browser.current_window_handle
        qqHandle = allhandles[1]
        browser.switch_to.window(qqHandle)
        # 同时qq登陆内嵌页面要切换表单:ctrl+shift+f搜索:frame,找到frame name = ptlogin_iframe
        browser.switch_to.frame("ptlogin_iframe")
        # 选择输入密码登陆
        bt_opt_inputAP = browser.find_element_by_xpath('//div[@id="bottom_qlogin"]')
        bt_opt_inputAP.click()
        # 开始模拟输入账号密码登陆
        text_qq_account = browser.find_element_by_id("u")
        text_qq_password = browser.find_element_by_id("p")
        bt_qq_login = browser.find_element_by_id("login_button")
        text_qq_account.send_keys(account)
        text_qq_password.send_keys(password)
        bt_qq_login.click()
        time.sleep(3)
        # 切回知乎窗口,表单不用
        browser.switch_to.window(zhihuHandle)
        # 4.返回含指定键值的cookie字典
        # zhihucookie是列表含有多项,每项都是字典类型;这里不用txt保存,故无需解析成json数据
        zhihu_cookies = browser.get_cookies()
        cookies_dic = {}  # 只保存键值name,value对应的数据
        for cookie in zhihu_cookies:
            cookies_dic[cookie['name']] = cookie['value']
        browser.close()
        return cookies_dic

    def zhihu_phoneNum_login(self, account, password, captcha_savePath):
        """
        循环模拟登陆知乎,直至出现的是英文验证码,输入验证码登陆
        :param account: 账号
        :param password: 密码
        :return: 尝试手机号登陆,返回只含有键值name,value对应字典cookie
        """
        count = 0
        while 1:
            # 1.打开浏览器
            browser = webdriver.Chrome()
            browser.get('http://www.zhihu.com')
            # 2.查找登陆元素
            # 1.点击登陆
            bt_opt_login = browser.find_element_by_xpath('//div[@class="SignContainer-switch"]/span')
            bt_opt_login.click()

            # 2.输入账号密码
            time.sleep(1)
            # elem_account = browser.find_element_by_name("username")
            # elem_pwd = browser.find_element_by_name("password")
            text_account = browser.find_element_by_xpath('//input[@name="username"]')
            text__pwd = browser.find_element_by_xpath('//input[@name="password"]')
            text_account.send_keys(account)
            text__pwd.send_keys(password)

            # 3.模拟点击登陆
            bt_login = browser.find_element_by_xpath('//button[@type="submit"]')
            bt_login.click()

            # 4.处理验证码登陆
            # 1.获取验证码图片
            time.sleep(1)  # 等待一会儿验证码图片还没加载
            # noinspection PyBroadException
            try:
                # 定位到中文验证码,刷新浏览器重新登陆。
                img_captcha = browser.find_element_by_class_name("Captcha-chineseImg") 
                print("中文字符暂时不能处理,第{0}次重试...\n".format(count + 1))
                browser.refresh()
                time.sleep(1)
                continue
            except Exception as e:
                # 定位中文验证码出错,则说明定位到英文验证码,开始识别人工输入
                # 验证码是服务器生成b64编码,直接进行解码写入保存即可,不用下载
                # 定位到验证码元素
                img_captcha = browser.find_element_by_class_name("Captcha-englishImg")
                # 获取元素内的验证码b64编码
                img64_src = img_captcha.get_attribute("src")
                # 对编码进行一些处理可解码
                img64_src = img64_src.replace("data:image/jpg;base64,", "")  # 删除前面标识
                img64_src = img64_src.replace("%0A", "\n")  # get_attribute会把\n替换成%OA,要替换回来
                img_date = b64decode(img64_src)
                # 2.保存图片
                img_path = captcha_savePath + "\\captcha.png"
                with open(img_path, "wb") as f:  # 将验证码图片以二进制流写入保存
                    f.write(img_date)
                # 3.打开图片
                os.system(img_path)
                captcha = input("已捕捉到英文验证码,请在打开的图片识别输入:")
                time.sleep(2)
                # 4. 输入验证码登陆
                elem_captcha_input = browser.find_element_by_xpath('//input[@name="captcha"]')
                elem_captcha_input.send_keys(captcha)
                # 5.登陆
                bt_login1 = browser.find_element_by_xpath('//button[@type="submit"]')
                bt_login1.click()
                time.sleep(2)

            # 5.已经成功登陆,返回含指定键值的cookie字典
                # zhihucookie是列表含有多项,每项都是字典类型;这里不用txt保存,故无需解析成json数据
                print("成功登陆!即将开始下载...")
                zhihu_cookies = browser.get_cookies()
                cookies_dic = {}  # 只保存键值name,value对应的数据
                for cookie in zhihu_cookies:
                    cookies_dic[cookie['name']] = cookie['value']
                browser.close()
                return cookies_dic

    def download(self, cookies_dic, answer_url, img_savepath, pages, limit=20):
        """
        下载知乎指定任意问题下图片
        :param cookies_dic: 知乎cookie
        :param answer_url: 知乎问题url
        :param img_savepath: 知乎问题下图片保存路径
        :param pages: 下载多少页
        :param limit: 一页默认限定20篇回答
        :return:
        """
        # 爬十页,每页爬20篇回答,共200篇
        for p in range(pages):
            dest_url = answer_url.format(limit, p * limit)
            response = requests.get(dest_url, headers=self.headers, cookies=cookies_dic)
            # 爬取的是json格式,要读取分析用json.loads载入
            json_date = json.loads(response.text)
            # 保存这20篇回答:作者、答案中图片
            authors = jsonpath.jsonpath(json_date, '$..author.name')
            contents = jsonpath.jsonpath(json_date, '$..content')
            # 循环在指定root_path创建以作者名命名的文件夹,里面放有相应回答照片
            for l in range(limit):
                # 创建以作者名命名的文件夹
                author_path = img_savepath + "\\知乎照片1\\" + authors[l]
                if not os.path.exists(author_path):
                    os.makedirs(author_path)
                # 用re正则解析对应的回答内容里面的图片
                img_pattern = re.compile('data-original="(.*?)"')
                img_urls = re.findall(img_pattern, contents[l])  # 相邻两个重复
                new_img_urls = []
                index = 0
                # url相邻重复,去除
                for url in img_urls:
                    if index % 2 == 0:
                        new_img_urls.append(url)
                    index += 1
                # 判断是否有图片
                if len(new_img_urls) == 0:
                    print("无照片")
                    continue
                # 下载图片
                count = 0
                for url in new_img_urls:
                    img_path = author_path + "\\" + str(count) + ".jpg"
                    response_img = requests.get(url)
                    with open(img_path, "wb") as f:
                        f.write(response_img.content)  
                    count += 1
                print("第{0}篇回答照片下载完毕".format(p * limit + l + 1))
            time.sleep(5)  # 等待5s,开始下载下一页
        print("下载完毕!")


if __name__ == '__main__':
    answer_url = "https://www.zhihu.com/api/v4/questions/275359100/answers?include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%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%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%2Cis_labeled%2Cis_recognized%2Cpaid_info%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%2A%5D.topics&limit={0}&offset={1}&platform=desktop&sort_by=default"
    desktop_path = "C:\\Users\\86151\\Desktop"
    spider = zhihuSpider()
    # cookies_dir = spider.zhihu_qq_login("380141****", "riguangyu*****")
    cookies_dir = spider.zhihu_phoneNum_login("1517948****", "riguangyu***", desktop_path)
    spider.download(cookies_dir, answer_url, desktop_path, 10)
    

四、总结


   呼呼,终于完成了!最开始接触爬虫,便想着爬取知乎小姐姐照片,成功的那一刻还是挺有成就感的。下一篇博文便是记录破解bilibili滑块验证码了,你滑任你滑,我爬我的虫~

   下次再见啦!对了,俺也不是要求太多,这么详细的知乎爬虫记录,各位看官老爷不点个关注/喜欢嘛?谢谢您的鼓励~

  • 11
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值