模拟登录知乎

这几天在研究模拟登录, 以知乎 - 与世界分享你的知识、经验和见解为例。实现过程遇到不少疑问,借鉴了知乎xchaoinfo的代码,万分感激!

知乎登录分为邮箱登录和手机登录两种方式,通过浏览器的开发者工具查看,我们通过不同方式登录时,网址是不一样的。邮箱登录的地址email_url = 'https://www.zhihu.com/login/email',手机登录网址是phone_url = 'http://www.zhihu.com/login/phone_num'

1. 建立一个可以传Cookie的opener

Cookie用来跟踪用户是否已经登录的状态信息。一旦网站认证了我们的登录,就会将cookie存到浏览器中,里面包含了服务器生成的令牌、登录有效时长、状态跟踪信息。当登陆有效时长达到,我们的登录状态就被清空,想要访问其他需要登录后才能访问的页面也就不能成功了。意思就是我们能保持登录状态不掉线,是因为服务器端核对了我们访问时携带的cookie,核对成功后服务器端就认为我们时“已经登录了”的状态。urllib.requst.urlopen()可传入的参数中,都不能携带cookie信息。http.cookiejarFileCookieJar可以帮到我们,FileCookieJar可以将cookie存到本地文件中。 其中FileCookieJar的子类LWPCookieJar,可以存Set-Cookie3类型的文件,而MozillaCookieJar子类是存为.txt格式的文件。这里我使用LWPCookieJar

filename = 'cookie'
cookie = http.cookiejar.LWPCookieJar(filename)

这样就建立了一个可以保存cookie的实例对象,它还有一个方法load()可以从本地加载已存的cookie数据,这样我们就可以携带着cookie(相当于带了一块令牌)访问服务器,服务器核对成功后,就可以访问那些登录后才能访问的页面。

try:
    cookie.load(ignore_discard=True)
except IOError:
    print('Cookie未加载!')

其中参数ignore_discard=True表示即使cookies将被丢弃也把它保存下来,它还有另外一个参数igonre_expires表示当前数据覆盖(overwritten)原文件。

现在建立一个可以处理cookie的opener。

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 '
                         '(KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36',
           "Host": "www.zhihu.com",
           "Referer": "https://www.zhihu.com/",
           }
# 创建一个可以处理cookies的opener
opener = request.build_opener(request.HTTPCookieProcessor(cookie))
# 给openner添加headers, addheaders属性接受元组而非字典
opener.addheaders = [(key, value) for key, value in headers.items()]

接下来我们可以使用opener.open()来传入url和data了。

2. 获取登录所需关键参数

模拟登录知乎,除了要POST自己的账号密码,还有两个动态生成的参数,一个是_xsrf,还有一个就是讨厌的验证码了。我们输入账号密码后,通过开发者工具找到一个名为email的json文件,请求方法是POST,可以看到其域名是https://www.zhihu.com/login/email.我用火狐的开发者工具,点开这个文件看到里面的参数。

与chrome对应的好像是Headers下面的Form Data,在Network里面勾选preserve log就可以看到。

这里就把本人邮箱和密码的给打码了哈,嘿嘿。

获取_xsrf

这个参数是动态变化了,所以不能获取一次后就一劳永逸。从html的body里面搜索下_xsrf,然后用正则表达式匹配出来就行。

def get_xsrf():
    """
    获取参数_xsrf
    """
    req = opener.open('https://www.zhihu.com')
    html = req.read().decode('utf-8')
    get_xsrf_pattern = re.compile(r'<input type="hidden" name="_xsrf" value="(.*?)"')
    # 这里会返回多个值,不过都是一样的内容,取第一个就行
    _xsrf = re.findall(get_xsrf_pattern, html)[0]
    return _xsrf

获取验证码

知乎登录一般需要填入验证码,如果没有post这个参数过去,是不能成功登录的。

知乎的验证码开始把我给坑了,在html内容的页面里搜索能看到验证码图片的网址,但是实际用xxx.read().decode('utf-8')获取到的网页内容是没有这个网址的,它被隐藏了!好狡诈。研究无果只好搜索,从知乎上这个问题xchaoinfo的回答找到答案,结果是这个图片网址中的一串数字就是时间戳

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

通过time.time()可以查看当前时间戳。比如我当前是1471771678.5400066,看是不是和上面红框中的很接近!通过过换算关系t = time.time() * 1000可以得到图片链接的关键部分,其余部分都是不变的。验证码的完整地址为captcha_url = 'http://www.zhihu.com/captcha.gif?r=' + t + "&type=login"。好了,链接知道了,下载下来查看并手动输入就行了。再把这个post过去应该就可以登录成功了,好激动。

def get_captcha():
    """
    获取验证码本地显示
    返回你输入的验证码
    """
    t = str(int(time.time() * 1000))
    # 验证码完整网址
    captcha_url = 'http://www.zhihu.com/captcha.gif?r=' + t + "&type=login"
    # 下载验证码图片,用下面注释掉的方法一样的效果
    request.urlretrieve(captcha_url, 'cptcha.gif')
    # image_data = request.urlopen(captcha_url).read()
    # with open('cptcha.gif', 'wb') as f:
    #     f.write(image_data)
    # 用Pillow库显示图片,免去手动去文件夹打开的麻烦
    im = Image.open('cptcha.gif')
    im.show()
    captcha = input('本次登录需要输入验证码: ')
    return captcha

3. 尝试模拟登录知乎

关键的两个东西我们都获取到了,现在登录一下试试吧!要将登录方式考虑进去,如果检测到用于输入手机号,则我们应该访问手机登录网址;否则就是邮箱登录。

def login(username, password):
    """
    输入自己的账号密码,模拟登录知乎
    """
    # 检测到11位数字则是手机登录
    if re.match(r'\d{11}$', account):
        url = 'http://www.zhihu.com/login/phone_num'
        data = {'_xsrf': get_xsrf(),
                'password': password,
                'remember_me': 'true',
                'phone_num': username
                }
    else:
        url = 'https://www.zhihu.com/login/email'
        data = {'_xsrf': get_xsrf(),
                'password': password,
                'remember_me': 'true',
                'email': username
                }

    # 若不用验证码,直接登录
    post_data = parse.urlencode(data).encode('utf-8')
    r = opener.open(url, post_data)
    result = r.read().decode('utf-8')
    # 打印返回的响应,r = 1代表响应失败,msg里是失败的原因
    # 要用验证码,post后登录
    if (json.loads(result))["r"] == 1:
        data['captcha'] = get_captcha()
        post_data = parse.urlencode(data).encode('utf-8')
        r = opener.open(url, post_data)
        result = r.read().decode('utf-8')
        print((json.loads(result))['msg'])
    # 保存cookie到本地
    cookie.save(ignore_discard=True, ignore_expires=True)

关于那句print((json.loads(result))['msg']) ,opener携带数据post过去,请求网址得到的响应会返回登录信息,该数据是json类型,刚开始我一直是登录失败的,返回这样的玩意儿!

{

"r": 1,

"errcode": 1991829,

"data": {"captcha":"\u9a8c\u8bc1\u7801\u9519\u8bef"},

"msg": "\u9a8c\u8bc1\u7801\u9519\u8bef"

}

r为1表示登录失败,0表示登录成功。msg里反映的是登录失败的原因是”验证码会话失效“。

这里一定注意,登录是一个连贯的过程。这个过程我们总共有三次访问网址,一定保证包括获取动态参数,获取验证码、最终模拟登陆都使用同一个opener。这也是登录失败的原因之一,因为刚开始获取_xsrf和验证码时用的是urlopen()urllib标准库并不很强大,可以尝试requests库,会让这个过程变得简单。

看下结果吧。如果显示登录成功,可以访问以下个人资料的网址,这个如果登录失败了是不能查看的,会返回到登录界面的网址。

好了,这次模拟登录感觉与前两次学习比起来难度加深了,好多问题还得靠搜索才能解决。加油吧。

这里贴上全部代码。

import re
from urllib import parse, request
import http.cookiejar
from PIL import Image
import time
import json

# 建立LWPCookieJar实例,可以存Set-Cookie3类型的文件。
# 而MozillaCookieJar类是存为'.txt'格式的文件
cookie = http.cookiejar.LWPCookieJar('cookie')
# 若本地有cookie则不用再post数据了
try:
    cookie.load(ignore_discard=True)
except IOError:
    print('Cookie未加载!')

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 '
                         '(KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36',
           "Host": "www.zhihu.com",
           "Referer": "https://www.zhihu.com/",
           }
opener = request.build_opener(request.HTTPCookieProcessor(cookie))
# 给openner添加headers, addheaders方法接受元组而非字典
opener.addheaders = [(key, value) for key, value in headers.items()]

def get_xsrf():
    """
    获取参数_xsrf
    """
    response = opener.open('https://www.zhihu.com')
    html = response.read().decode('utf-8')
    get_xsrf_pattern = re.compile(r'<input type="hidden" name="_xsrf" value="(.*?)"')
    _xsrf = re.findall(get_xsrf_pattern, html)[0]
    return _xsrf

def get_captcha():
    """
    获取验证码本地显示
    返回你输入的验证码
    """
    t = str(int(time.time() * 1000))
    captcha_url = 'http://www.zhihu.com/captcha.gif?r=' + t + "&type=login"
    # 获取验证码也用同一个opener
    image_data = opener.open(captcha_url).read()
    with open('cptcha.gif', 'wb') as f:
        f.write(image_data)
    im = Image.open('cptcha.gif')
    im.show()
    captcha = input('本次登录需要输入验证码: ')
    return captcha

def login(username, password):
    """
    输入自己的账号密码,模拟登录知乎
    """
    # 检测到11位数字则是手机登录
    if re.match(r'\d{11}$', account):
        url = 'http://www.zhihu.com/login/phone_num'
        data = {'_xsrf': get_xsrf(),
                'password': password,
                'remember_me': 'true',
                'phone_num': username
                }
    else:
        url = 'https://www.zhihu.com/login/email'
        data = {'_xsrf': get_xsrf(),
                'password': password,
                'remember_me': 'true',
                'email': username
                }

    # 若不用验证码,直接登录
    post_data = parse.urlencode(data).encode('utf-8')
    r = opener.open(url, post_data)
    result = r.read().decode('utf-8')
    # 打印返回的响应,r = 1代表响应失败,msg里是失败的原因
    # 要用验证码,post后登录
    if (json.loads(result))["r"] == 1:
        data['captcha'] = get_captcha()
        post_data = parse.urlencode(data).encode('utf-8')
        r = opener.open(url, post_data)
        result = r.read().decode('utf-8')
        print((json.loads(result))['msg'])
    # 保存cookie到本地
    cookie.save(ignore_discard=True, ignore_expires=True)

def isLogin():
    # 通过查看用户个人信息来判断是否已经登录
    url = 'https://www.zhihu.com/settings/profile'
    # 获得真实网址,可能重定向了
    actual_url = opener.open(url).geturl()
    if actual_url == 'https://www.zhihu.com/settings/profile':
        return True
    else:
        return False

if __name__ == '__main__':
    if isLogin():
        print('您已经登录')
    else:
        account = input('输入账号:')
        secret = input('输入密码:')
        login(account, secret)

更新: 使用requests库重写程序

Date:2016.8.23

今天翻看了下requests的文档,学了点urllib库再看这个不算很难。我用requests最基本的函数重新实现以上功能,当然大部分代码是重复的。

requests.get()类似urllib.request.urlopen()。如其名是以get方式请求的,接收url,字典形式的headerstimeoutallow_redirects等参数,当然还有requests.post(),可以传入data参数,不像urllib一样需要对字典形式的data进行编码,requests它会自动处理并且data可以传入json数据。

allow_redirects这是参数可选TrueFalse,默认True,若选False则表示禁止重定向,按我的理解即禁止自动跳转。

看下两个库的区别,返回来的response可选textcontent,其中text以文本形式返回,content以二进制数据形式返回,比如我们请求的网址是图片,就返回content,便可以以wb方式写入文件了。看下这两个库在实现返回网页内容的区别。对了返回的对象如response还有一个属性是status_code访问成功了当然就返回的200啦。

# requests返回html内容
response = requests.get('https://www.zhihu.com', headers=headers)
    html = response.text
# urllib返回html内容
req = urllib.request.Request(url)
response = urllib.request.urlopen(req)
html = response.read().decode('utf-8')

然后就是requests.Session()或者requests.session(),两种写法都可以。类型都是一个class 'requests.sessions.Session',看下源码其实session返回的就是一个Session对象。requests.Session()会新建一个会话,可以把同一用户的不同请求联系起来,直到会话结束都会自动处理cookies,这比urllib方便多了。如果只使用requests.get()或者requests.post()每次访问网页都是独立进行的,并没有把当前用户的多次访问关联起来,故而模拟登录需要用到requests.Session()。然后再用新建的session使用post()get()等函数。如下。

session = requests.Session()
session.get(url, headers)
session.post(url, headers, data)

这只是requests的冰山一角!它强大着呢。号称是HTTP FOR HUMAN。不过掌握以上基本的东西,足够重写模拟登录知乎的程序。进一步学习请看requests文档,那里有详细介绍。

再放requests版本的。

import re
import requests
import http.cookiejar
from PIL import Image
import time
import json

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 '
                         '(KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36',
           "Host": "www.zhihu.com",
           "Referer": "https://www.zhihu.com/",
           }
# 建立一个会话,可以把同一用户的不同请求联系起来;直到会话结束都会自动处理cookies
session = requests.Session()
# 建立LWPCookieJar实例,可以存Set-Cookie3类型的文件。
# 而MozillaCookieJar类是存为'/.txt'格式的文件
session.cookies = http.cookiejar.LWPCookieJar("cookie")
# 若本地有cookie则不用再post数据了
try:
    session.cookies.load(ignore_discard=True)
except IOError:
    print('Cookie未加载!')

def get_xsrf():
    """
    获取参数_xsrf
    """
    response = session.get('https://www.zhihu.com', headers=headers)
    html = response.text
    get_xsrf_pattern = re.compile(r'<input type="hidden" name="_xsrf" value="(.*?)"')
    _xsrf = re.findall(get_xsrf_pattern, html)[0]
    return _xsrf

def get_captcha():
    """
    获取验证码本地显示
    返回你输入的验证码
    """
    t = str(int(time.time() * 1000))
    captcha_url = 'http://www.zhihu.com/captcha.gif?r=' + t + "&type=login"
    response = session.get(captcha_url, headers=headers)
    with open('cptcha.gif', 'wb') as f:
        f.write(response.content)
    # Pillow显示验证码
    im = Image.open('cptcha.gif')
    im.show()
    captcha = input('本次登录需要输入验证码: ')
    return captcha

def login(username, password):
    """
    输入自己的账号密码,模拟登录知乎
    """
    # 检测到11位数字则是手机登录
    if re.match(r'\d{11}$', account):
        url = 'http://www.zhihu.com/login/phone_num'
        data = {'_xsrf': get_xsrf(),
                'password': password,
                'remember_me': 'true',
                'phone_num': username
                }
    else:
        url = 'https://www.zhihu.com/login/email'
        data = {'_xsrf': get_xsrf(),
                'password': password,
                'remember_me': 'true',
                'email': username
                }
    # 若不用验证码,直接登录
    result = session.post(url, data=data, headers=headers)
    # 打印返回的响应,r = 1代表响应失败,msg里是失败的原因
    # loads可以反序列化内置数据类型,而load可以从文件读取
    if (json.loads(result.text))["r"] == 1:
        # 要用验证码,post后登录
        data['captcha'] = get_captcha()
        result = session.post(url, data=data, headers=headers)
        print((json.loads(result.text))['msg'])
        # 保存cookie到本地
    session.cookies.save(ignore_discard=True, ignore_expires=True)

def isLogin():
    # 通过查看用户个人信息来判断是否已经登录
    url = "https://www.zhihu.com/settings/profile"
    # 禁止重定向,否则登录失败重定向到首页也是响应200
    login_code = session.get(url, headers=headers, allow_redirects=False).status_code
    if login_code == 200:
        return True
    else:
        return False

if __name__ == '__main__':
    if isLogin():
        print('您已经登录')
    else:
        account = input('输入账号:')
        secret = input('输入密码:')
        login(account, secret)

以上使用到的浏览器UA都是电脑端的,现在知乎出了倒立文字验证码。如果因此而导致登录失败,可以将浏览器UA改成Android端或者IOS端。


by sunhaiyu

2016.8.21

Python爬虫初学(三)—— 模拟登录知乎的更多相关文章

  1. python爬虫scrapy框架——人工识别登录知乎倒立文字验证码和数字英文验证码(2)

    操作环境:python3 在上一文中python爬虫scrapy框架--人工识别知乎登录知乎倒立文字验证码和数字英文验证码(1)我们已经介绍了用Requests库来登录知乎,本文如果看不懂可以先看之前 ...

  2. requests_模拟登录知乎

    如何登录知乎? 首先要分析,进行知乎验证的时候,知乎服务器需要我们提交什么数据,提交的地址.先进行几次登录尝试,通过浏览器中network中查看数据流得知,模拟登录知乎需要提供5个数据,分别是_xsr ...

  3. Python爬虫初学(二)—— 爬百度贴吧

    Python爬虫初学(二)-- 爬百度贴吧 昨天初步接触了爬虫,实现了爬取网络段子并逐条阅读等功能,详见Python爬虫初学(一). 今天准备对百度贴吧下手了,嘿嘿.依然是跟着这个博客学习的,这次仿照 ...

  4. 利用scrapy模拟登录知乎

    闲来无事,写一个模拟登录知乎的小demo. 分析网页发现:登录需要的手机号,密码,_xsrf参数,验证码 实现思路: 1.获取验证码 2.获取_xsrf 参数 3.携带参数,请求登录 验证码url : ...

  5. Java爬虫——模拟登录知乎

    登录界面,首先随意输入一个账号,登录查看发送表单的请求 可以发现请求是Post : https://www.zhihu.com/login/phone_num 发送的表单是 _xsrf: passwo ...

  6. 在Python中用Request库模拟登录(三):Discuz论坛(未加密,有验证码,有隐藏验证)

    以Discuz的官方站为例.直接点击网页右上角的登录按钮,会弹出一个带验证码的登录窗口.输入验证码之后,会检查验证码是否正确.然后登录.首先,通过抓包分析,这些过程浏览器和服务器交换了哪些数据. 抓包 ...

  7. [Python爬虫] Selenium实现自动登录163邮箱和Locating Elements介绍

    前三篇文章介绍了安装过程和通过Selenium实现访问Firefox浏览器并自动搜索"Eastmount"关键字及截图的功能.而这篇文章主要简单介绍如何实现自动登录163邮箱,同时 ...

  8. Python手动构造Cookie模拟登录后获取网站页面内容

    最近有个好友让我帮忙爬取个小说,这个小说是前三十章直接可读,后面章节需要充值VIP可见.所以就需要利用VIP账户登录后,构造Cookie,再用Python的获取每章节的url,得到内容后再使用 PyQ ...

  9. 在Python中用Request库模拟登录(四):哔哩哔哩(有加密,有验证码)

    !已失效! 抓包分析 获取验证码 获取加密公钥 其中hash是变化的,公钥key不变 登录 其中用户名没有被加密,密码被加密. 因为在获取公钥的时候同时返回了一个hash值,推测此hash值与密码加密 ...

随机推荐

  1. UOJ79 一般图最大匹配

    题目描述 从前一个和谐的班级,所有人都是搞OI的.有 nn 个是男生,有 00 个是女生.男生编号分别为 1,-,n1,-,n. 现在老师想把他们分成若干个两人小组写动态仙人掌,一个人负责搬砖另一个人 ...

  2. CANopen学习——OSI模型复习

    CanOpen协议实现了物理层和数据链路层,OSI模型是完整的7层. OSI模型网络七层包括物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 物理层:提供信息传输的物理连接通道,包括使用的 ...

  3. 个人作业-Week3

    个人作业-Week3 1. 软件工程师的成长 同学们在上这门课的时候,还是大三,你的困难和迷茫,别人一定有过.请看看别人怎么学习的,有些是科班,有些是野路子,有些成功,有些失败. 请读完下面所有博客( ...

  4. Linux chroot 并使用之前系统设备节点

    /********************************************************************************* * Linux chroot 并使 ...

  5. flex的用途

    一.可以利用flex来布局一个div在另一个div里面水平垂直居中 如:html代码: <div class="container"> <div class=&q ...

  6. for循环与for循环嵌套

    今天温习了下分支语句跟for循环,主要讲解了for循环嵌套,这里开始有点迷糊了,整理下思路在做练习 for循环嵌套用我自己的大白话来说就是一个外圈的for程序里面一个套着一个小的for程序,如果在范围 ...

  7. Match:Blue Jeans(POJ 3080)

    DNA序列 题目大意:给你m串字符串,要你找最长的相同的连续字串 这题暴力kmp即可,注意要按字典序排序,同时,是len<3才输出no significant commonalities #in ...

  8. Spring 4 官方文档学习(十一)Web MVC 框架之编码式Servlet容器初始化

    在Servlet 3.0+ 环境中,你可以编码式配置Servlet容器,用来代替或者结合 web.xml文件.下面是注册DispatcherServlet : import org.springfra ...

  9. dedecms 忘记后台密码

    找到admin表 dede_admin,把其pwd的值修改为 默认的 字符串:f297a57a5a743894a0e4, 之后,你的密码就被充值为 admin

  10. 编码规范(二)之Code Templates的设置(转)

    http://swiftlet.net/archives/1199 编码规范(二)之Code Templates的设置(转) 文件(Files)注释标签:/** * @Title: ${file_na ...