Python爬虫实战之(四)| 模拟登录京东商城

4,955 阅读8分钟

作者:xiaoyu

微信公众号:Python数据科学

知乎:Python数据分析师


前两篇和大家分享了爬虫中http的一些概念和使用方法,基础篇我们主要介绍了http的请求头,高级篇我们主要介绍了cookiesession(具体可以点击上面链接进行回顾)。但其实在爬虫中还有很多关于http的内容需要了解,例如 tokenoauth等。对于这些概念博主将在后续文章中逐一的详细介绍,本篇主要针对前两篇内容与大家分享一个模拟登录的实战例子。

开始想以知乎为例,但是看到网上关于知乎模拟登录的教程太多了,所以就以“京东”为例。

大家都知道,京东是不需要登录就可以访问主页内容的,因此模拟登录的意义在于查看个人信息,比如可以获取个人的交易信息(购物车商品,购物历史记录,待收货商品信息等),或者卖家的商品销售信息和评论等等。

好了,了解背景过后,让我们开始模拟登录吧。

准备工作

大家都知道,模拟登录其实就是通过http的post请求方式来提交用户信息的(用户名和密码)。对于浏览器而言,只输入用户名和密码就可以登陆了(偶尔有验证码),那是因为浏览器在背后都帮你处理好了。而爬虫的模拟登录过程需要我们自己解决,因此我们需要弄清楚浏览器的那些背后操作是如何进行的才能对症下药。

难点分析:

  • 寻找提交表单所需字段信息
  • cookie信息的获取和使用
  • 验证码的处理

我们打开浏览器,博主用的Chrome浏览器。首先使用Ctrl+Shift+N进入干净的无痕模式,防止之前的cookie数据造成干扰。

输入了京东的登陆网址 passport.jd.com/new/login.a…,进入如下登录界面。

表单字段信息

现在我们通过开发者工具来看看浏览器背后都干了什么吧。有的朋友提问到,输入用户名和密码后页面直接跳转到主页面了,看不到我们要的数据了。其实这里只需要故意将你的密码输错不进入跳转就可以解决了。

点击登录,然后我们看到有个FormData,这就是浏览器每次向服务器提交的表单信息。

第一眼看过去感觉快要无望了。但是别着急,这些字段信息其实都是有处可寻的。我们Ctrl+U打开京东登录页面的源码里,然后Ctrl+F 试着搜一搜这些字段信息。

先搜第一个uuid字段,发现它就在源码中,紧着后面是一些其它的字段信息,那就齐活了。我们看到除了loginname,nloginpwd,authcode,其他的字段全都是hidden的类型,也就是被隐藏了的字段。

好了,那下一步就自然知道干什么了。我们可以直接请求登录页面源码提取字段信息了。

Cookie的处理

Cookie可以通过使用http的Cookiejar定制opener进行获取,也可以直接使用requests模块来实现。

requests模块实现起来比较方便,因为内部已经封装好了自动处理Cookie的功能。第一次发送请求可以通过服务器获取Cookie,后续的请求则会自动带着已获取的Cookie信息进行发送。当然,也可以手动添加Cookie,手动添加的Cookie优先级高,将会覆盖默认的信息。

为了说明模拟登录的用法,本篇博主将使用简便的requests模块来完成对Cookie的处理。

验证码的处理

验证码的处理方法也有多种,可以分为自动识别的和手动识别

  • 手动处理验证码就是通过验证码链接将验证码图片下载到本地,然后手动敲入完成信息录入。
  • 自动识别是使用一些高级的算法技术来完成的,可以使用OCR智能图文识别,机器学习进行识别训练等。

本篇将选择手动录入验证码,旨在理解模拟登录的过程。

代码实现

初始信息配置

class JD_crawl:
    def __init__(self, username, password):
        self.headers = {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36'
                                      ' (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36',
                        'Referer': 'https://www.jd.com/',
                        }
        self.login_url = "https://passport.jd.com/new/login.aspx"
        self.post_url = "https://passport.jd.com/uc/loginService"
        self.auth_url = "https://passport.jd.com/uc/showAuthCode"
        self.session = requests.session()
        self.username = username
        self.password = password
  • 创建了一个JD_crawl的类,设置了实例的headerssession会话对象,以及三个后面请求需要用到的url。
  • 因为整个登录是一个完整的过程,所以后续若干请求需要共同使用同一个session会话对象

提取表单登录信息

    def get_login_info(self):
        html = self.session.get(self.login_url, headers=self.headers).content
        soup = BeautifulSoup(html, 'lxml')

        uuid = soup.select('#uuid')[0].get('value')
        eid = soup.select('#eid')[0].get('value')
        fp = soup.select('input[name="fp"]')[0].get('value')  # session id
        _t = soup.select('input[name="_t"]')[0].get('value')  # token
        login_type = soup.select('input[name="loginType"]')[0].get('value')
        pub_key = soup.select('input[name="pubKey"]')[0].get('value')
        sa_token = soup.select('input[name="sa_token"]')[0].get('value')

        auth_page = self.session.post(self.auth_url,
                                      data={'loginName': self.username, 'nloginpwd': self.password}).text
        print(auth_page)
        if 'true' in auth_page:
            auth_code_url = soup.select('#JD_Verification1')[0].get('src2')
            auth_code = str(self.get_auth_img(auth_code_url))
        else:
            auth_code = ''

        data = {
            'uuid': uuid,
            'eid': eid,
            'fp': fp,
            '_t': _t,
            'loginType': login_type,
            'loginname': self.username,
            'nloginpwd': self.password,
            'chkRememberMe': True,
            'pubKey': pub_key,
            'sa_token': sa_token,
            'authcode': auth_code
            }
        return data
  • 首先对登录的login_url发起请求,获取登陆页面源码后通过BeautifulSoup解析工具ccs选择器来提取隐藏字段信息。
  • loginname,nloginpwd,authcode三个非隐藏字段需要用户手动录入。
  • 对于是否需要录入验证码的问题,可以通过请求https://passport.jd.com/uc/showAuthCode(代码中的auth_url)来判断。

请求结果是一个如下格式的字符串。

请求结果: ({"verifycode":xxx})
xxx:true 或者 false

因此可以简单的查看结果中是否有true来判断是否需要验证码。

  • 如果为true就需要调用验证码函数方法,将验证码图片下载,输入图片上的验证码,并赋给authcode字段进行表单提交完成登录。
  • 如果为false则不需要验证码,authcode字段为空字符串。

一般当我们多次输入了错误的账号或密码时,构成安全危险,就会提示输入验证码。

获取验证码

    def get_auth_img(self, url):
        auth_code_url = 'http:{}&yys={}'.format(url, str(int(time.time()*1000)))
        auth_img = self.session.get(auth_code_url, headers=self.headers)
        with open('authcode.jpg', 'wb') as f:
            f.write(auth_img.content)
        code_typein = input('请根据下载图片输入验证码:')
        return code_typein
  • 从源码获取的验证码链接是一个相对链接src2
     src2="//authcode.jd.com/verify/image?a=1&acid=dcb4370b-2763-44e6-83ff-4b89bc01193d&uid=dcb4370b-2763-44e6-83ff-4b89bc01193d"

因此,我们需要将它补全,在src2前面拼接字符串 http: 。但是当我们尝试这个url的时候会发现图片并没有下载成功,为什么呢?

因为还需要在结尾加上时间戳,接着看后边的 onclick,它的字符串中有和src2完全一样的链接,但在结尾处多了 &yys= ''。通过观察内容发现有 datetime 字样,于是可以判断这可能是一个时间戳字符串

onclick="this.src= document.location.protocol +'//authcode.jd.com/verify/image?a=1&acid=dcb4370b-2763-44e6-83ff-4b89bc01193d&uid=dcb4370b-2763-44e6-83ff-4b89bc01193d&yys='+new Date().getTime();$('#authcode').val('');"

小提示:

时间戳(引自百度百科):

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

时间戳在Python中可以用time模块来完成:

time.time()*1000
  • 将验证码图片存为jpg格式,储存在项目文件目录下。

可以看到图片就在目录下,我们双击打开然后按照图片输入验证码。

模拟登录

    def login(self):
        data = self.get_login_info()
        headers = {
                    'Referer': self.post_url,
                    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36'
                                  ' (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36',
                    'X-Requested-With': 'XMLHttpRequest'
                  }
        try:
            login_page = self.session.post(self.post_url, data=data, headers=headers)
            print(login_page.text)
        except Exception as e:
            print(e)
  • 根据获取的表单登录信息进行提交登录。
  • 请求的url是 https://passport.jd.com/uc/loginService
  • 注意这里的 Service 中的 "S" 是大写。

如果登录成功,则会显示以下字符串。

({"success":"http://www.jd.com"})

登录验证及结果

为了验证登录真的成功了,现将博主的购物车拿出来做实验,如果在获取源码中找到了指定商品名称,那么就说明成功了。

好吧,都是之前随便放的,就以牙线为测试目标吧。

    def shopping(self):
        carshop = self.session.post('https://cart.jd.com/cart.action', headers=self.headers)
        print(carshop.text)

简单的请求了一下购物车url。在下载的源码中搜索“牙”,然后找了目标。

代码链接:github.com/xiaoyusmd/j…

总结

本篇主要介绍了京东商城的模拟登录方法,当然还有一些网站的登录机制比较复杂,比如weibo登录需要调用api,需要我们详细阅读api说明。

后续将会分享更多模拟登录的内容,欢迎大家指正。

参考链接: https://github.com/xchaoinfo/fuck-login http://blog.csdn.net/weixin_38206454/article/details/78655209?locationNum=2&fps=1


关注微信公众号Python数据科学,获取 120G 人工智能 学习资料。