爬虫-模拟登陆

前言

前天看到一个爬取了知乎50多万评论的帖子, 羡慕的同时也想自己来尝试一下。看看能不能获取一些有价值的信息。

必备知识点

下面简单的来谈谈我对常见的防爬虫的一些技巧的理解。

headers

现在很多服务器都对爬虫进行了限制,有一个很通用的处理就是检测“客户端”的headers。通过这个简单的判断就可以判断出客户端是爬虫程序还是真实的用户。(虽然这一招在Python中可以很轻松的解决)。

Referer

referer字段很实用,一方面可以用于站内数据的防盗链。比如我们经常遇到的在别处复制的图片链接,粘到我们的博客中出现了“被和谐”的字样。
这就是referer起到的作用,服务器在接收到一个请求的时候先判断Referer是否为本站的地址。如果是的话就返回正确的资源;如果不是,就返回给客户端预先准备好的“警示”资源。


Referer字段

所以再写爬虫的时候(尤其是爬人家图片的时候),加上Referer字段会很有帮助。

User-Agent

User-Agent字段更是没的说了。相信绝大部分有防爬长处理的网站都会判断这个字段。来检测客户端是爬虫程序还是浏览器。

如果是爬虫程序(没有添加header的程序),服务器肯定不会返回正确的内容啦;如果包含了这个字段,才会进行到下一步的防爬虫处理操作。
如果网站仅仅做到了这一步,而你的程序又恰好添加了User-Agent,基本上就可以顺利的蒙混过关了。


User-Agent字段

隐藏域

很多时候,我们模拟登录的时候需要提交的数据并不仅仅是用户名密码,还有一些隐藏域的数据。比如拿咱们CSDN来说,查看登录页
https://passport.csdn.net/account/login

的时候,你会发现源码中有这样的内容:


隐藏域

也就是说,如果你的程序仅仅post了username和password。那么是不可能进入到webflow流程的。因为服务器端接收请求的时候还会判断有没有lt和execution这两个隐藏域的内容。

其他

防止爬虫还有很多措施,我本人经验还少,所以不能在这里一一列举了。如果您有相关的经验,不妨留下评论,我会及时的更新到博客中,我非常的赞同大家秉承学习的理念来交流。

模拟登录

在正式的模拟登录知乎之前,我先来写个简单的小例子来加深一下印象。

模拟防爬

模拟防爬肯定是需要服务器端的支持了,下面简单的写一下来模拟整个过程。

服务器端

login.php

先来看看: login.php

<?php
/**
 * @Author: 郭 璞
 * @File: login.php
 * @Time: 2017/4/7
 * @Contact: 1064319632@qq.com
 * @blog: http://blog.csdn.net/marksinoberg
 * @Description:  模拟防爬处理
 **/

$username = $_POST['username'];
$password = $_POST['password'];
$token = $_POST['token'];

if (!isset($token)) {
    echo "登录失败!";
    exit(0);
}else{
    // 这里简单的模拟一下token的计算规则,实际中会比这更加的复杂
    $target_token = $username.$username;
    if ($token == $target_token){
        if ($username ==='123456' and $password==='123456'){
            echo "登陆成功!<br>用户名: ".$username."<br>密码:".$password."<br>token: ".$token;
        }else{
            echo "用户名或密码错误!";
        }
    }else{
        echo "token 验证失败!";
    }
}

login.html

相对应的前端代码简单的写成下面: login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>郭璞的小窝</title>
</head>
<body>

<form action="login.php" method="post">
    用户名: <input type="text" name="username" id="username" required><br>
    密&nbsp;&nbsp;码:<input type="password" name="password" required><br>
    <input type="hidden" name="token" id="token" value="">
    <hr>
    <input type="submit" value="登录">
</form>
<script>
    document.getElementById('username').onblur = function() {
        var username = document.getElementById("username").value;
        var token = document.getElementById('token');
        token.value = username+username;
    }
</script>

</body>
</html>
浏览器测试
正常提交用户名密码的话如下:
正确提交信息

我们不难发现,服务器端和客户端使用了相同的计算规则,这样的话我们就可以实现对客户端的登录请求进行一次简答的甄选了。正常的浏览器请求都是没有问题的。

用户名或者密码填写错误的情况如下:
用户名密码出错的情况
爬虫没有添加隐藏域时

用爬虫程序运行的话,如果没有添加隐藏域的内容,我们就不可能正确地登录了。那么先来看下这样的傻瓜式爬虫是怎么失效的吧。
使用Python写一个这样的爬虫用不了多少代码,那么就用Python来写吧。其他的接口测试工具postman,selenium等等也都是很方便的,这里暂且不予考虑。

# coding: utf8

# @Author: 郭 璞
# @File: sillyway.py                                                                 
# @Time: 2017/4/7                                   
# @Contact: 1064319632@qq.com
# @blog: http://blog.csdn.net/marksinoberg
# @Description: 傻瓜式爬虫未添加隐藏域的值

import requests

url = "http://localhost/phpstorm/pachong/login.php"

payload = {
    'username': '123456',
    'password': '123456'
}

response = requests.post(url=url, data=payload)
print(response.text)

运行的结果如下:


傻瓜式爬虫,未添加隐藏域信息

对比PHP文件对于请求的处理,我们可以更加轻松的明白这个逻辑。

添加了隐藏域的爬虫

正如上面失败的案例,我们明白了要添加隐藏域的值的必要性。那么下面来改进一下。

因为我们”不知道”服务器端是怎么对token处理的具体的逻辑。所以还是需要从客户端的网页下手。
且看下面的图片。


客户端隐藏域内容获取

注意:这里仅仅是为了演示的方便,采用了对username字段失去焦点时计算token。实际上在网页被拉取到客户端浏览器的时候, 服务器会事先计算好token的值,并赋予到token字段的。所以大可不必计较这里的实现。

Python代码

# coding: utf8

# @Author: 郭 璞
# @File: addhiddenvalue.py                                                                 
# @Time: 2017/4/7                                   
# @Contact: 1064319632@qq.com
# @blog: http://blog.csdn.net/marksinoberg
# @Description: 添加了隐藏域信息的爬虫

import requests

## 先获取一下token的内容值,方便接下来的处理
url = 'http://localhost/phpstorm/pachong/login.php'

payload = {
    'username': '123456',
    'password': '123456',
    'token': '123456123456'
}

response = requests.post(url, data=payload)
print(response.text)

实现效果如下:


添加了token域内容的爬虫效果

现在是否对于隐藏域有了更深的认识了呢?

知乎模拟登录

按照我们刚才的逻辑,我们要做的就是:

先打开预登陆界面,目标:得到必须提交的隐藏域的值
然后通过post再次访问该路径(准备好了一切必须的信息)
获取网页内容并进行解析,或者做其他的处理。

思路很清晰了,下面就可以直接上代码了。

# coding: utf8

# @Author: 郭 璞
# @File: ZhiHuLogin.py                                                                 
# @Time: 2017/4/7                                   
# @Contact: 1064319632@qq.com
# @blog: http://blog.csdn.net/marksinoberg
# @Description: 模拟登陆知乎
import re
from bs4 import BeautifulSoup
import subprocess, os
import json
import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Host": "www.zhihu.com",
    "Upgrade-Insecure-Requests": "1",
}

############################# 从邮箱方式登录
loginurl = 'http://www.zhihu.com/login/email'
session = requests.session()
html = session.get(url=loginurl, headers=headers).text
soup = BeautifulSoup(html, 'html.parser')
print(soup)
xsrf_token = soup.find('input', {'name':'_xsrf'})['value']
print("登录xsrf_token: "+xsrf_token)

############################ 下载验证码备用
checkcodeurl = 'http://www.zhihu.com/captcha.gif'
checkcode = session.get(url=checkcodeurl, headers=headers).content
with open('./checkcode.png', 'wb') as f:
    f.write(checkcode)
print('已经打开验证码,请输入')
# subprocess.call('./checkcode.png', shell=True)
os.startfile(r'checkcode.png')
checkcode = input('请输入验证码:')
os.remove(r'checkcode.png')
############################ 开始登陆
payload = {
    '_xsrf': xsrf_token,
    'email': input('请输入用户名:'),
    'password': getpass.getpass(prompt="请输入密码:"),#input('请输入密码:'),
    'remeber_me': 'true',
    'captcha': checkcode
}
response = session.post(loginurl, data=payload)
print("*"*100)

result = response.text
print("登录消息为:"+result)
tempurl = 'https://www.zhihu.com/question/57964452/answer/155231804'
tempresponse = session.get(tempurl, headers=headers)
soup = BeautifulSoup(tempresponse.text, 'html.parser')
print(soup.title)

实现的效果如下

模拟登录知乎效果
模拟登录知乎效果

观察动态图,不难发现对于https://www.zhihu.com/question/57964452/answer/155231804

界面,我们正确的获取到了title的内容。(也许你会说,正常访问也会获取到这个内容的,但是我们是从已登录的session上获取的,请记住这一点哈。)。

更新版知乎模拟登陆

代码部分

# coding: utf8

# @Author: 郭 璞
# @File: MyZhiHuLogin.py                                                                 
# @Time: 2017/4/8                                   
# @Contact: 1064319632@qq.com
# @blog: http://blog.csdn.net/marksinoberg
# @Description: 我的模拟登录知乎

import requests
from bs4 import BeautifulSoup
import os, time
import re
# import http.cookiejar as cookielib

# 构造 Request headers
agent = 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36'
headers = {
    "Host": "www.zhihu.com",
    "Referer": "https://www.zhihu.com/",
    'User-Agent': agent
}

######### 构造用于网络请求的session
session = requests.Session()
# session.cookies = cookielib.LWPCookieJar(filename='zhihucookie')
# try:
#     session.cookies.load(ignore_discard=True)
# except:
#     print('cookie 文件未能加载')

############ 获取xsrf_token
homeurl = 'https://www.zhihu.com'
homeresponse = session.get(url=homeurl, headers=headers)
homesoup = BeautifulSoup(homeresponse.text, 'html.parser')
xsrfinput = homesoup.find('input', {'name': '_xsrf'})
xsrf_token = xsrfinput['value']
print("获取到的xsrf_token为: ", xsrf_token)

########## 获取验证码文件
randomtime = str(int(time.time() * 1000))
captchaurl = 'https://www.zhihu.com/captcha.gif?r='+\
             randomtime+"&type=login"
captcharesponse = session.get(url=captchaurl, headers=headers)
with open('checkcode.gif', 'wb') as f:
    f.write(captcharesponse.content)
    f.close()
# os.startfile('checkcode.gif')
captcha = input('请输入验证码:')
print(captcha)

########### 开始登陆
headers['X-Xsrftoken'] = xsrf_token
headers['X-Requested-With'] = 'XMLHttpRequest'
loginurl = 'https://www.zhihu.com/login/email'
postdata = {
    '_xsrf': xsrf_token,
    'email': '邮箱@qq.com',
    'password': '密码'
}
loginresponse = session.post(url=loginurl, headers=headers, data=postdata)
print('服务器端返回响应码:', loginresponse.status_code)
print(loginresponse.json())
# 验证码问题输入导致失败: 猜测这个问题是由于session中对于验证码的请求过期导致
if loginresponse.json()['r']==1:
    # 重新输入验证码,再次运行代码则正常。也就是说可以再第一次不输入验证码,或者输入一个错误的验证码,只有第二次才是有效的
    randomtime = str(int(time.time() * 1000))
    captchaurl = 'https://www.zhihu.com/captcha.gif?r=' + \
                 randomtime + "&type=login"
    captcharesponse = session.get(url=captchaurl, headers=headers)
    with open('checkcode.gif', 'wb') as f:
        f.write(captcharesponse.content)
        f.close()
    os.startfile('checkcode.gif')
    captcha = input('请输入验证码:')
    print(captcha)

    postdata['captcha'] = captcha
    loginresponse = session.post(url=loginurl, headers=headers, data=postdata)
    print('服务器端返回响应码:', loginresponse.status_code)
    print(loginresponse.json())




##########################保存登陆后的cookie信息
# session.cookies.save()
############################判断是否登录成功
profileurl = 'https://www.zhihu.com/settings/profile'
profileresponse = session.get(url=profileurl, headers=headers)
print('profile页面响应码:', profileresponse.status_code)
profilesoup = BeautifulSoup(profileresponse.text, 'html.parser')
div = profilesoup.find('div', {'id': 'rename-section'})
print(div)

验证效果

更新版知乎模拟登陆

总结

经过了今天的测试,发现自己之前对于网页的处理理解的还是不够到位。

  • 对于“静态页面”,常用的urllib, requests应该是可以满足需要的了。

  • 对于动态页面的爬取,可以使用无头浏览器PhantomJS,Selenium等来实现。

但是一直处理的不够精简,导致在爬一些重定向页面的过程中出现了很多意想不到的问题。

在这块的爬虫程序还有很多地方需要进行完善啊。

另外模拟登录还有一个利器,那就是cookie。下次有时间的话再来学习一下使用cookie来实现。今天就先到这里吧。


参考链接:
http://blog.csdn.net/shell_zero/article/details/50783078

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 刚开始接触Python,看很多人玩爬虫我也想玩,找来找去发现很多人用网络爬虫干的第一件事就是模拟登陆,增加点难度就...
    李牧羊阅读 6,204评论 8 27
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • http://www.91ri.org/tag/fuzz-bug 通常情况下,有三种方法被广泛用来防御CSRF攻击...
    jdyzm阅读 4,156评论 0 5
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,431评论 25 707
  • 之前是在朋友圈里看到了一个关于牙膏的研究,看了后,想着来写写,目的第一是为了让那些可能没有看到过的朋友能够在短时间...
    Jensen95阅读 1,445评论 0 2