最新 详解如何模拟登录知乎 英文验证码以及中文倒立字(继上篇scrapy分析知乎)

可转至我的博客:http://www.dwlufvexyu.com/%e6%9c%80%e6%96%b0-%e8%af%a6%e8%a7%a3%e5%a6%82%e4%bd%95%e6%a8%a1%e6%8b%9f%e7%99%bb%e5%bd%95%e7%9f%a5%e4%b9%8e%ef%bc%882-0-%e6%8c%81%e7%bb%ad%e6%9b%b4%e6%96%b0%ef%bc%81%ef%bc%89/

开始更新:

废话不多讲,一样的直接入正题;

爬取知乎,需要登录;只要登录获得了cookie,便可反复利用

那么首先说说如何最简单的登录知乎?看我的另一篇博文

接下来开始一步一步操作:

在这里插入图片描述
首先,第一步:直接 进行 “检查”; 获取到 “密码登录”的xpath元素(直接复制即可)。

#切换登录
change_login = browser.find_element_by_xpath('//*[@id="root"]/div/main/div/div/div[1]/div/form/div[1]/div[2]')
#进行点击
change_login.click()

Step 2. 直接“检查”到 账号以及密码框;

可以看到<账号 是input name="username" 以及 密码 name="password">

在这里插入图片描述
在这里插入图片描述
然后直接用find_element_by_name找到 对应输入框,然后利用send_keys进行传值。这里为了避免以前登陆过,进页面会有存有的账号密码导致重复输入,所以先执行ctrl+A进行输入框内容的选中,再进行传值。
在这里插入图片描述
这里手机号以13579246810,密码admin123为例 ;如图,这里设置延时,为了得到更好的查看效果,为了速度可以不要。

#可以设置延时 
time.sleep(1)
#采用ctrl+a 进行选中输入 进行覆盖 避免追加输入导致账号密码错误
browser.find_element_by_name("username").send_keys(Keys.CONTROL+"a")
browser.find_element_by_name("username").send_keys('13579246810')
time.sleep(1)
browser.find_element_by_name("password").send_keys(Keys.CONTROL + "a")
browser.find_element_by_name("password").send_keys('admin123')
time.sleep(1)

step3.账号密码输入完毕后,进行确定。同样进行 “检查” 找到“确定”的xpath元素。再进行click() ; 等待10s进行判断,这里等待10s是为了 假如登陆成功,需要一定时间进行加载页面;”

login = browser.find_element_by_xpath('//*[@id="root"]/div/main/div/div/div[1]/div/form/button')
login.click()
#这里如果直接登录成功,则等待10s等待跳转后的页面加载完成
time.sleep(10)
#如果是登录False
login_success=False

那么随机找到一个登陆成功页面的xpath元素,那么就可以判断是已登陆成功,反之False; 如找到如下元素div的xpath,如果存在,那么为True;
在这里插入图片描述

while not login_success:
    try:
        notify_ele=browser.find_element_by_class_name('Popover PushNotifications AppHeader-notifications')
        login_success=True
    except:
        pass

如果不存在,登陆失败,则pass继续操作,进行判断中/英文验证码

英文验证码:

在这里插入图片描述
中文同样的:
在这里插入图片描述

#判断英文验证码还是倒立字验证码
try:
    english_captcha_element = browser.find_element_by_class_name('Captcha-englishImg')
except:
    english_captcha_element = None
try:
    chinese_captcha_element = browser.find_element_by_class_name('Captcha-chineseImg')
except:
    chinese_captcha_element = None

接下来就先识别英文二维码吧:

有三种打码方式 : 编码实现(tesseract-ocr) 识别率低 , 在线打码(云打码) ,人工打码(后两者识别率更高,通过平台提供的API),通常使用在线打码(也就是云打码)

这里我们用 “云打码” 平台(比较好用的在线打码平台),进入官网,我们创建一个用户 以及 一个开发者账号(账号密码可设置为一样) 方便测试;关于为什么 需要创建两个账户,可以百度。这里简要说一下 ,开发者通过平台给用户提供调用API,当用户在线打码成功,开发者会获得相应的分成利润。

如👇,可以看到我的开发者用户,测试已经获取0.1的分成了。

在这里插入图片描述
用户界面,每次打码都会消耗积分
在这里插入图片描述
进入开发者中心,下载最新云打码接口在这里插入图片描述
在这里插入图片描述
下好之后,记得改好文件;如下
在这里插入图片描述
现在来仔细看英文验证码:从图中可以明显看出是base64加密,且有一段是多余的,除此之外有个坑还~仔细看有个%0A也是多余的
在这里插入图片描述
所以我们先解码

if english_captcha_element:
    #这里解码同中文倒立字
    #取src
    base64_text = english_captcha_element.get_attribute("src")
    import base64
    #打上断点来debug看看code是什么 
    #替换
    code = base64_text.replace("data:image/jpg;base64,","").replace("%0A","")
    #print(code) 
    f = open("yzm_en.jpeg", "wb")
    f.write(base64.b64decode(code))
    f.close()

多达75次的%0A
在这里插入图片描述
在这里插入图片描述

接下来就是调用云打码平台提供的api,下面的账号密码是注册的用户的账号密码~

验证码code 如果为空,就继续打码

#采用云解码提供的API 在线打码
#导入之前云打码下载的那个包
from douban.tools.yundama_requests import YDMHttp
#username passwd appID appKey
yundama=YDMHttp("username","passwd",'appid','appkey')
#图片名 及 打码方式
code=yundama.decode("yzm_en.jpeg",5000,60)
#判断是否成功
while True:
    if code == "":
        code = yundama.decode("yzm_en.jpeg",5000,60)
    else:
        break
#成功后自动输入验证码;同样send_keys传入验证码值~
browser.find_element_by_xpath('//*[@id="root"]/div/main/div/div/div[1]/div/form/div[4]/div/div/label/input').send_keys(code)

如下可以看到已经识别成功了~~~
在这里插入图片描述
在这里插入图片描述
然后就直接登录成功了~~


接下来讲关于中文验证码的识别:

ps:关于中文验证码的识别,有些还不太够详细甚至有点草率,后面完成互联网+ 项目后,找时间进行完善。

首先是在github上search "zhiye" 大佬

download后,cp到当前工程目录下

现需要安装依赖包

在这里插入图片描述
在这里插入图片描述
除了注释以外的,都需要install;
如果有些加了 豆瓣源依旧无法下载成功,那么到链接下载,然后离线安装whl。安装中,有什么问题可以在下方留言。
对于如何调用,在“zheye”下方就可以看到;在这里插入图片描述
通过使用方法,我们用自带的a.gif做测试
在这里插入图片描述
在这里插入图片描述

"[(50.66702980958077, 39.79269455429982), (47.53827248468947, 278.193248033293)]"虽然座标是这样的,但其实实际上并不是

我们进入zheye的在线测试地址:http://zheye.74ls74.org/;同样的我们提交a.gif

在这里插入图片描述
打开json文件,可以看到,座标是这样的,(关于两个字的先后顺序,不重要,只要是准确的就可以)
在这里插入图片描述
实际上首先座标是有点问题的,看上图可以明显看出,第二个字的x座标更大,y座标更小,但是显示却是颠倒的,所以需要进行座标的调换,除此之外,知乎倒立字为1-2个;所以作出以下判断

from zheye import zheye
z = zheye()
positions = z.Recognize('D:/Pythonstudy/douban/douban/spiders/zhihu_image/a.gif')
last_position = []
#只有两种情况 一个倒立字 和 两个倒立字
#两个倒立字时
if len(positions) == 2:
    #进行座标的调整 直接复制即可 ,因为 默认是有问题的
    if positions[0][1] > positions[1][1]:
        last_position.append([positions[1][1], positions[1][0]])
        last_position.append([positions[0][1], positions[0][0]])
    else:
        last_position.append([positions[0][1], positions[0][0]])
        last_position.append([positions[1][1], positions[1][0]])
#print(last_position)
#一个倒立字时
else:
    last_position.append([positions[0][1], positions[0][0]])

得到正确的座标,应该是这样的
在这里插入图片描述
座标确立之后,接下来的任务就简单了。
进行move,以及click。但是实际上知乎的倒立字,单独打开和页面上的大小不一致;如图

在这里插入图片描述
所以做如下变化,关于代码这里还有个坑,同时做实验时,需要把下方的chrome下载任务关掉,否则座标点击错误。且需要关注;

ele_location=chinese_captcha_element.location
##
x_relative=ele_location["x"]
y_relative=ele_location["y"]
#这里是减去 浏览器上方工具栏的高度 保证Y座标的精度
browser_navigation_panel_height=browser.execute_script('return window.outerHeight - window.innerHeight;')

在这里插入图片描述

#两个倒立字时:
#第一个倒立字的座标 除2是因为 知乎的验证码 比 本地保存的小 2倍
first_position=[int(last_position[0][0] / 2),int(last_position[0][1] / 2)]
second_position=[int(last_position[1][0] / 2),int(last_position[1][1] / 2)]
#鼠标move及click
move(x_relative+first_position[0],y_relative+browser_navigation_panel_height+first_position[1])
click()
#等待3s 点击第二个
time.sleep(3)
move(x_relative + second_position[0],
     y_relative + browser_navigation_panel_height + second_position[1])
click()

#一个倒立字时:
first_position = [int(last_position[0][0] / 2), int(last_position[0][1] / 2)]
move(x_relative + first_position[0],
     y_relative + browser_navigation_panel_height + first_position[1])
click()

倒立字点击后,点击登录即可;最终就成功了;

附上知乎所有源码🐎~另一篇只有分析知乎的。

一些参数需要自己修改成自己的~

# -*- coding: utf-8 -*-
import json

import scrapy
import re
from selenium import webdriver
from urllib import parse
from scrapy.loader import ItemLoader
from douban.items import ZhihuAnswerItem,ZhihuQuestionItem
import datetime

from zheye import zheye


from mouse import move,click

import time

from selenium.webdriver.common.keys import Keys


class ZhihuSpider(scrapy.Spider):
    name = 'zhihu'
    allowed_domains = ['www.zhihu.com']
    start_urls = ['https://www.zhihu.com/']

    start_answer_url="https://www.zhihu.com/api/v4/questions/{0}/answers?include=data%5B*%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%2Cpaid_info_content%3Bdata%5B*%5D.mark_infos%5B*%5D.url%3Bdata%5B*%5D.author.follower_count%2Cbadge%5B*%5D.topics&offset={1}&limit={2}&sort_by=default&platform=desktop"

    headers = {
        # "HOST": "www.zhihu.com",
        # "Referer": "https://www.zhizhu.com",
        #User-Agent必不可少
        'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"
    }
    def parse(self, response):
        #xpath检测知乎标题 取出所有a标签的href_url
        all_urls = response.xpath("//a/@href").extract()
        #接下来遍历list类型的all_urls
        #因为
        all_urls = [parse.urljoin(response.url, url) for url in all_urls]
        for url in all_urls:
            # print(url)
            match_obj=re.match("(.*zhihu.com/question/(\d+)).*",url)
            if match_obj:
                request_url=match_obj.group(1)
                yield scrapy.Request(request_url,headers=self.headers,callback=self.parse_question)
                # break
            else:
                yield scrapy.Request(url,headers=self.headers,callback=self.parse)
                # print(request_url,question_id)

    def parse_question(self,response):
        match_obj = re.match("(.*zhihu.com/question/(\d+)).*", response.url)
        if match_obj:
            question_id = int(match_obj.group(2))
        item_loader=ItemLoader(item=ZhihuQuestionItem(),response=response)
        print(response)
        item_loader.add_css("title", 'h1.QuestionHeader-title::text')
        item_loader.add_css('content', '.QuestionHeader-detail')
        item_loader.add_value('url', response.url)
        item_loader.add_value('zhihu_id', question_id)
        item_loader.add_css('answer_num', '.List-headerText span::text')
        item_loader.add_css('comments_num', '.QuestionHeader-Comment button::text')
        item_loader.add_css('topics', '.QuestionHeader-topics .Popover div::text')
        item_loader.add_css('watch_user_num', '.NumberBoard-itemValue::text')

        question_item = item_loader.load_item()
        yield scrapy.Request(self.start_answer_url.format(question_id, 0, 20), headers=self.headers, callback=self.parse_answer)
        yield question_item
    def parse_answer(self,response):
        ans_json = json.loads(response.text)
        is_end = ans_json["paging"]["is_end"]
        next_url = ans_json["paging"]["next"]
        totals_num=ans_json["paging"]["totals"]
        #提取具体字段
        for answer in ans_json["data"]:
            answer_item = ZhihuAnswerItem()
            answer_item["zhihu_id"] = answer["id"]
            answer_item["url"] = answer["url"]
            answer_item["question_id"] = answer["question"]["id"]
            answer_item["author_id"] = answer["author"]["id"] if "id" in answer["author"] else None
            answer_item["content"] = answer["content"] if "content" in answer else None
            answer_item["praise_num"] = answer["voteup_count"]
            answer_item["comments_num"] = answer["comment_count"]
            answer_item["create_time"] = answer["created_time"]
            answer_item["update_time"] = answer["updated_time"]
            answer_item["crawl_time"] = datetime.datetime.now()

            yield answer_item
        if not is_end:
            yield scrapy.Request(next_url,headers=self.headers,callback=self.parse_answer)







    def start_requests(self):


        # cookies=pickle.load(open("D:/Pythonstudy/douban/douban/spiders/cookies/zhihu_cookies","rb"))
        # cookie_dict = {}
        #
        # for cookie in cookies:
        #     cookie_dict[cookie['name']] = cookie['value']
        #
        # return [scrapy.Request(url=self.start_urls[0], dont_filter=True, cookies=cookie_dict)]



        # 1.cmd 设置端口 启动chrome (启动之前确保所有chrome实例已经关闭 否则不成功)
        # 一行 js 代码识别 Selenium+Webdriver 及其应对方案 知乎的反爬措施 确定是否为模拟登录 window.navigator.webdriver
        ##验证码操作

        chrome_option=webdriver.ChromeOptions()
        chrome_option.add_argument('--disable-extensions')
        chrome_option.add_experimental_option('debuggerAddress',"127.0.0.1:9222")

        # browser = webdriver.Chrome()
        browser=webdriver.Chrome(executable_path="D:/Envs/py3scrapy/Scripts/chromedriver.exe",chrome_options=chrome_option)
        browser.get("https://www.zhihu.com/")

        #最大化窗口,确保定位准确 如果已经是最大化 则pass
        try:
            browser.maximize_window()
        except:
            pass

        # time.sleep(30)
        # cookies = browser.get_cookies()
        #
        # pickle.dump(cookies, open("D:/Pythonstudy/douban/douban/spiders/cookies/zhihu_cookies",'wb'))
        #
        # cookie_dict = {}
        #
        # for cookie in cookies:
        #     cookie_dict[cookie['name']] = cookie['value']
        #
        # return [scrapy.Request(url=self.start_urls[0], dont_filter=True, cookies=cookie_dict)]

        #进行切换登录及登录操作 延时看你心情
        change_login = browser.find_element_by_xpath('//*[@id="root"]/div/main/div/div/div[1]/div/form/div[1]/div[2]')
        change_login.click()
        time.sleep(1)
        #采用ctrl+a 进行选中输入 进行覆盖 避免追加输入导致账号密码错误
        browser.find_element_by_name("username").send_keys(Keys.CONTROL+"a")
        browser.find_element_by_name("username").send_keys('num')
        time.sleep(1)
        browser.find_element_by_name("password").send_keys(Keys.CONTROL + "a")
        browser.find_element_by_name("password").send_keys('passwd')
        time.sleep(1)
        login = browser.find_element_by_xpath('//*[@id="root"]/div/main/div/div/div[1]/div/form/button')
        login.click()
        #这里如果直接登录成功,则等待10s等待跳转后的页面加载完成
        time.sleep(10)
        #如果是登录False
        login_success=False
        while not login_success:
            try:
                notify_ele=browser.find_element_by_class_name('Popover PushNotifications AppHeader-notifications')
                login_success=True
            except:
                pass

            #判断英文验证码还是倒立字验证码
            try:
                english_captcha_element = browser.find_element_by_class_name('Captcha-englishImg')
            except:
                english_captcha_element = None
            try:
                chinese_captcha_element = browser.find_element_by_class_name('Captcha-chineseImg')
            except:
                chinese_captcha_element = None

            #如果是中文验证码
            if chinese_captcha_element:
                #进行倒立字图片的定位
                ele_location=chinese_captcha_element.location
                ##
                x_relative=ele_location["x"]
                y_relative=ele_location["y"]
                #这里是减去 浏览器上方工具栏的高度 保证Y座标的精度
                browser_navigation_panel_height=browser.execute_script('return window.outerHeight - window.innerHeight;')

                #取倒立字的src,并保存在本地进行base64的解码
                base64_text=chinese_captcha_element.get_attribute("src")
                import base64
                #这里 %0A 很奇葩
                code=base64_text.replace("data:image/jpg;base64,","").replace("%0A","")
                f=open("yzm_cn.jpeg","wb")
                f.write(base64.b64decode(code))
                f.close()
                #这里进行倒立字的座标确定 github:zheye
                z=zheye()
                #图片路径
                positions = z.Recognize('yzm_cn.jpeg')

                last_position = []
                #只有两种情况 一个倒立字 和 两个倒立字
                #两个倒立字时
                if len(positions) == 2:
                    #进行座标的调整 直接复制即可 ,因为 默认是有问题的
                    if positions[0][1] > positions[1][1]:
                        last_position.append([positions[1][1], positions[1][0]])
                        last_position.append([positions[0][1], positions[0][0]])
                    else:
                        last_position.append([positions[0][1], positions[0][0]])
                        last_position.append([positions[1][1], positions[1][0]])
                    #第一个倒立字的座标 除2是因为 知乎的验证码 比 本地保存的小 2倍
                    first_position=[int(last_position[0][0] / 2),int(last_position[0][1] / 2)]
                    second_position=[int(last_position[1][0] / 2),int(last_position[1][1] / 2)]
                    #鼠标move及click
                    move(x_relative+first_position[0],y_relative+browser_navigation_panel_height+first_position[1])
                    click()
                    #等待3s 点击第二个
                    time.sleep(3)
                    move(x_relative + second_position[0],
                         y_relative + browser_navigation_panel_height + second_position[1])
                    click()

                #只有一个倒立字时
                else:
                    last_position.append([positions[0][1], positions[0][0]])
                    ###
                    first_position = [int(last_position[0][0] / 2), int(last_position[0][1] / 2)]
                    move(x_relative + first_position[0],
                         y_relative + browser_navigation_panel_height + first_position[1])
                    click()

                #倒立字点击之后 重新进行账号密码的输入
                browser.find_element_by_name("username").send_keys(Keys.CONTROL + "a")
                browser.find_element_by_name("username").send_keys('num')
                time.sleep(1)
                browser.find_element_by_name("password").send_keys(Keys.CONTROL + "a")
                browser.find_element_by_name("password").send_keys('passwd')
                time.sleep(1)
                login = browser.find_element_by_xpath('//*[@id="root"]/div/main/div/div/div[1]/div/form/button')
                login.click()

            if english_captcha_element:
                #这里解码同中文倒立字
                base64_text = english_captcha_element.get_attribute("src")
                import base64
                code = base64_text.replace("data:image/jpg;base64,","").replace("%0A", "")
                f = open("yzm_en.jpeg", "wb")
                f.write(base64.b64decode(code))
                f.close()

                #采用云解码提供的API 在线打码
                from douban.tools.yundama_requests import YDMHttp
                #用户username passwd appID appKey
                #填写你自己的数据,需要自己修改
                appid=11111
                yundama=YDMHttp("user","passwd",appid,'appkey')
                #图片路径 及 打码方式
                code=yundama.decode("yzm_en.jpeg",5000,60)
                #判断是否成功
                while True:
                    if code == "":
                        code = yundama.decode("yzm_en.jpeg",5000,60)
                    else:
                        break
                #成功后自动输入验证码
                browser.find_element_by_xpath('//*[@id="root"]/div/main/div/div/div[1]/div/form/div[4]/div/div/label/input').send_keys(code)

                #重新输入账号密码
                browser.find_element_by_name("username").send_keys(Keys.CONTROL + "a")
                browser.find_element_by_name("username").send_keys('num')
                time.sleep(1)
                browser.find_element_by_name("password").send_keys(Keys.CONTROL + "a")
                browser.find_element_by_name("password").send_keys('passwd')
                time.sleep(1)
                login = browser.find_element_by_xpath('//*[@id="root"]/div/main/div/div/div[1]/div/form/button')
                login.click()

        pass



Thanks your watching!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章