JB的Python之旅-豆瓣自动顶贴功能

舒适小提示

全文加上代码总6.8k个字,阅读大约10分钟,谢谢你的点击,愿能解决你的问题;php

前言

前几天在小猪群里,有同窗问,有人知道怎么作豆瓣自动回复功能吗?而后群里就各类大神出马相助,各类填代码给资料的,也有同窗说用selenium模拟下就行了等等,其实你们都说的对,伸手党当然很差,可是考虑到让一个不了解的同窗去作这个事,的确有门槛,更别说查资料用selenium了;html

豆瓣回复功能尝试

一开始的想法,也是用selenium的,可是想着,仍是先模拟下,看看豆瓣的回复流程吧;
先打开须要回复的帖子,而后接着登陆豆瓣,而后回到刚刚这个帖子上,观察下界面,回复按钮就在底部,点击发送就是评论了;python

那咱们就抓包看下请求吧,浏览器按F12,选择network,点击左边红色按钮两次,把以前的数据都清除; linux

接着就输入内容,点击发送按钮,而后查看network界面,不难找到发送评论的请求,从名字上看,也能确认是发送评论的请求; 算法

而后看了下右边的请求数据,不难发现接口地址是:

https://www.douban.com/group/topic/121989778/add_comment
复制代码

并且发现,请求的时候,要带4个参数:数据库

ck=TXEg
rv_comment=层主真帅,赞赞赞  #这个就是要回复的内容
start=0
submit_btn=发送
复制代码

既然如此,那就用postman试一下吧:json

请求头参数用了常规的cookie、user-agent、referer、host; api

而body这块,虽然上面抓包看到有4个参数,可是实际验证只须要2个便可,发送的内容就是jbtest; 浏览器

那在postman上点击send,而后在那个帖子上刷新下页面,竟然能看到刚刚回复的内容服务器

看来,豆瓣回复功能只须要调接口就好了,都不用selenium了;

既然如此,不能写出下面这代码:

import requests
#豆瓣具体帖子回复的接口,格式是帖子连接+/add_comment
db_url = "https://www.douban.com/group/topic/121989778//add_comment"

headers = {
    "Host": "www.douban.com",
    "Referer": "https://www.douban.com/group/topic/121989778/?start=0",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 
    (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
    "Cookie":"your cookie"   #这里须要输入你本身的cookie信息,若是遇到转义字符,转
    义字符前面加\便可
}

params = {
    "ck": "TXEg",
    "rv_comment": "jbtest11111",#或者替换成想评论的东西
}
requests.post(db_url,headers=headers, data=params)
复制代码

上面的代码就是定义请求头跟body参数,像豆瓣的评论接口发一个请求便可,运行下脚本,刷新下网页:

没问题,回复功能多简单,so easy;

自动功能

嗯,回复功能能够了,那Python有没有相似定时器的功能?定时执行上面的post请求就行了?

答案是有的,那就是APScheduler

APScheduler简介

APScheduler是Python的一个定时任务框架,能够很方便的知足用户定时执行或者周期执行任务的需求,
它提供了基于日期date、固定时间间隔interval 、以及相似于Linux上的定时任务crontab类型的定时任务。
而且该框架不只能够添加、删除定时任务,还能够将任务存储到数据库中,实现任务的持久化,因此使用起来很是方便。

官方简介连接:apscheduler.readthedocs.io/en/3.3.1/

安装

1)利用pip安装:(推荐)

pip install apscheduler 
复制代码

2)基于源码安装:pypi.python.org/pypi/APSche…

python setup.py install 
复制代码

4种组件

APScheduler有四种组件:
1)triggers(触发器):
触发器包含调度逻辑,每个做业有它本身的触发器,用于决定接下来哪个做业会运行,除了他们本身初始化配置外,触发器彻底是无状态的。

2)job stores(做业存储):
用来存储被调度的做业,默认的做业存储器是简单地把做业任务保存在内存中,其它做业存储器能够将任务做业保存到各类数据库中,支持MongoDB、Redis、SQLAlchemy存储方式。
当对做业任务进行持久化存储的时候,做业的数据将被序列化,从新读取做业时在反序列化。

3)executors(执行器):
执行器用来执行定时任务,只是将须要执行的任务放在新的线程或者线程池中运行。
看成业任务完成时,执行器将会通知调度器。
对于执行器,默认状况下选择ThreadPoolExecutor就能够了,可是若是涉及到一下特殊任务如比较消耗CPU的任务则能够选择ProcessPoolExecutor,固然根据根据实际需求能够同时使用两种执行器。

4)schedulers(调度器):
调度器是将其它部分联系在一块儿,通常在应用程序中只有一个调度器,应用开发者不会直接操做触发器、任务存储以及执行器,相反调度器提供了处理的接口。
经过调度器完成任务的存储以及执行器的配置操做,如能够添加。修改、移除任务做业。 

APScheduler提供了多种调度器,经常使用的调度器有:

名称 场景
BlockingScheduler 适合于只在进程中运行单个任务的状况
BackgroundScheduler 适合于要求任何在程序后台运行的状况
AsyncIOScheduler 适合于使用asyncio框架的状况
GeventScheduler 适合于使用gevent框架的状况
TornadoScheduler 适合于使用Tornado框架的应用
TwistedScheduler 适合使用Twisted框架的应用
QtScheduler 适合使用QT的状况

简单的例子

from apscheduler.schedulers.blocking import BlockingScheduler
import time

# 实例化一个调度器
scheduler = BlockingScheduler()
 
def job1():
    print "%s: 执行任务"  % time.asctime()

# 添加任务并采用固定时间间隔,触发方式为3s一次
scheduler.add_job(job1, 'interval', seconds=3)

# 开始运行调度器
scheduler.start()
复制代码

执行后的效果:

很简单有没有,先初始化,而后add_job,最后start就行了,那下面,再详细讲解下不一样组件提供的功能吧;

定时任务

trigger提供任务的触发方式,共有3种方式:

  • date:只在某个时间点执行一次,用法:run_data(datetime|str)

    scheduler.add_job(my_job, 'date', run_date=date(2017, 9, 8), args=[]) scheduler.add_job(my_job, 'date', run_date=datetime(2017, 9, 8, 21, 30, 5), args=[]) scheduler.add_job(my_job, 'date', run_date='2017-9-08 21:30:05', args=[]) sched.add_job(my_job, args=[[])

    • interval: 每隔一段时间执行一次,用法:weeks=0 | days=0 | hours=0 | minutes=0 | seconds=0, start_date=None, end_date=None, timezone=None

    scheduler.add_job(my_job, 'interval', hours=2) scheduler.add_job(my_job, 'interval', hours=2, start_date='2017-9-8 21:30:00', end_date= '2018-06-15 21:30:00)

    @scheduler.scheduled_job('interval', id='my_job_id', hours=2) def my_job(): print("Hello World")

    • cron: 使用同linux下crontab的方式,即定时任务;,用法:(year=None, month=None, day=None, week=None, day_of_week=None, hour=None, minute=None, second=None, start_date=None, end_date=None, timezone=None)

    sched.add_job(my_job, 'cron', hour=3, minute=30) sched.add_job(my_job, 'cron', day_of_week='mon-fri', hour=5, minute=30, end_date='2017-10-30')

    @sched.scheduled_job('cron', id='my_job_id', day='last sun') def some_decorated_task(): print("I am printed at 00:00:00 on the last Sunday of every month!")

通常来讲,使用的比较多的是interval方式,能够重点留意下;

定时任务实战

1)APScheduler怎么设置范围时间任务,好比我想要在10:00~11:00这个范围的时间内随机一个时间点去执行任务

print(get_time()+"jbtest")
t= random.randint(1,10) # # 1~10秒随机
scheduler.add_job(myjob, 'interval', seconds=t,start_date='2018-09-05 10:00:00', end_date='2018-09-05 11:00:00')  # 估计就知足你的需求了吧
scheduler.start()
复制代码

2)若是不想具体的时间,而是某个范围的话:

3)区间直接 sched.scheduled_job('cron', day_of_week='mon-fri', hour='0-9', minute='30-59', second='*/3') 在周一到周五其间,天天的0点到9点之间,在30分到59分之间执行,执行频次为3秒。

任务操做

添加任务add_job
add_job能够返回一个apscheduler.job.Job实例,于是能够对它进行修改或者删除,而使用修饰器添加的任务添加以后就不能进行修改。

得到任务列表get_jobs
能够经过get_jobs方法来获取当前的任务列表,也能够经过get_job()来根据job_id来得到某个任务的信息。
而且apscheduler还提供了一个print_jobs()方法来打印格式化的任务列表。

scheduler.add_job(my_job, 'interval', seconds=5, id='my_job_id' name='test_job')
print scheduler.get_job('my_job_id')
print scheduler.get_jobs()
复制代码

修改任务 modify_job
修改任务的属性可使用apscheduler.job.Job.modify()或者modify_job()方法,能够修改除了id的其它任何属性。

job = scheduler.add_job(my_job, 'interval', seconds=5, id='my_job' name='test_job')
job.modify(max_instances=5, name='my_job')
复制代码

删除任务remove_job
删除调度器中的任务有能够用remove_job()根据job ID来删除指定任务或者使用remove(),
若是使用remove()须要事先保存在添加任务时返回的实例对象,任务删除后就不会在执行。

# 根据任务实例删除
job = scheduler.add_job(myfunc, 'interval', minutes=2)
job.remove()

# 根据任务id删除
scheduler.add_job(myfunc, 'interval', minutes=2, id='my_job_id')
scheduler.remove_job('my_job_id')
复制代码

任务的暂停pause_job和继续resume_job
暂停与恢复任务能够直接操做任务实例或者调度器来实现。
当任务暂停时,它的运行时间会被重置,暂停期间不会计算时间。

job = scheduler.add_job(myfunc, 'interval', minutes=2)
# 根据任务实例
job.pause()
job.resume()

# 根据任务id暂停
scheduler.add_job(myfunc, 'interval', minutes=2, id='my_job_id')
scheduler.pause_job('my_job_id')   # 暂停
scheduler.resume_job('my_job_id')   #恢复
复制代码

任务的修饰modify和重设reschedule_job

修饰:job.modify(max_instances=6, name='Alternate name')
重设:scheduler.reschedule_job('my_job_id', trigger='cron', minute='*/5')
复制代码

调度器操做

开启 scheduler.start()
可使用start()方法启动调度器,BlockingScheduler须要在初始化以后才能执行start(),
对于其余的Scheduler,调用start()方法都会直接返回,而后能够继续执行后面的初始化操做

from apscheduler.schedulers.blocking import BlockingScheduler
def my_job():
    print "Hello world!"
    scheduler = BlockingScheduler()
    scheduler.add_job(my_job, 'interval', seconds=5)
    scheduler.start()
复制代码

关闭 scheduler.shotdown(wait=True | False)
使用下边方法关闭调度器:

scheduler.shutdown() 
复制代码

默认状况下调度器会关闭它的任务存储和执行器,并等待全部正在执行的任务完成,若是不想等待,能够进行以下操做:

scheduler.shutdown(wait=False)
复制代码

暂停 scheduler.pause()
继续 scheduler.resume()

豆瓣自动回复

看了那么多APScheduler的简介,上面也有例子了,结合第一部分豆瓣的例子,不难写出下面的代码:

import requests
from apscheduler.schedulers.blocking import BlockingScheduler

 #豆瓣具体帖子回复的接口,格式是帖子连接+/add_comment
db_url = "https://www.douban.com/group/topic/121989778//add_comment"

scheduler = BlockingScheduler()

headers = {
    "Host": "www.douban.com",
    "Referer": "https://www.douban.com/group/topic/121989778/?start=0",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) 
    Chrome/68.0.3440.106 Safari/537.36",
    "Cookie":"your cookie"   #这里须要输入你本身的cookie信息,若是遇到转义字符,转
        义字符前面加\便可
}

params = {
    "ck": "TXEg",
    "rv_comment": "jbtest11111",
}

def my_job():
    requests.post(db_url,headers=headers, data=params)

#每隔10S就请求一次
scheduler.add_job(my_job,"interval",seconds=10,id="db")
scheduler.start()
复制代码

效果以下:

的确是每隔10S发送一次,good~

作到这里,你觉得完事了?
嗯,怎么说呢,若是是一个帖子的话,是完事了,可是以下是如下2种场景之一,还须要折腾:

1)同一个帖子,须要短期内回复,这个短期无法定义,可能
几分钟都算,除非是像正经常使用户几个小时回复一次就可能没问题;
2)N个帖子都要回复,并且回复间隔比较短,相似问题1;
复制代码

那这两种状况,会致使什么问题?固然是触发豆瓣的防爬虫啦:

目前发现,每次评论就算相隔1分钟,只要满3次,就必定会弹出这个验证码进行验证;

获取验证码ID

按照一开始的套路,那咱们F12看下输入验证码后发起请求的参数:

发现会在请求的带上一个叫 captcha-solution字段,value就是验证码内容,那咱们就异想天开的试试,用postman在body上加上这个验证码参数值,看可否评论?

验证码信息:

postman请求内容:

点击send,而后原来的网页刷新一下,结果以下:

对的,内容没有变,由于这样确定是不生效的,哪有那么容易;

验证码场景:

如今有网址T,有用户A和B两我的同事访问T
T给A返回的验证码是X,给B返回的验证码是Y,这两个验证码都正确
若是A输入B的验证码,是验证不经过的
复制代码

那服务器怎么区分A和B?那就是用cookie;

cookie是标示惟一身份的,好比有些网站,登陆一次后会自动登陆,可是若是清除了cookie,就没法自动登陆了,并且这cookie是个别人不同的; 说到这里,服务器后台生成验证码的流程就很容易理解了:

先随机生产一个随机字符串
而后和cookie绑定
再写到图片上返回给你
复制代码

更多的验证码生成信息,能够读一下这篇文章

此时,可能你有疑问,用postman的时候,cookie应该是跟PC点击发送是同样的,可是为何还不行?

由于cookie只是最简单的绑定条件,这么看来,豆瓣还有其余条件的,那咱们从新看一下,PC点击发送的时候,除了验证码,还有发送什么?

------WebKitFormBoundaryDjMAMsD95W3eYF1i
Content-Disposition: form-data; name="ck"

TXEg
------WebKitFormBoundaryDjMAMsD95W3eYF1i
Content-Disposition: form-data; name="rv_comment"

反反复复
------WebKitFormBoundaryDjMAMsD95W3eYF1i
Content-Disposition: form-data; name="img"; filename=""
Content-Type: application/octet-stream


------WebKitFormBoundaryDjMAMsD95W3eYF1i
Content-Disposition: form-data; name="captcha-solution"

produce
------WebKitFormBoundaryDjMAMsD95W3eYF1i
Content-Disposition: form-data; name="captcha-id"

woMrwYOVwn67NNfl9lv9vhRz:en
------WebKitFormBoundaryDjMAMsD95W3eYF1i
Content-Disposition: form-data; name="start"

0
------WebKitFormBoundaryDjMAMsD95W3eYF1i
Content-Disposition: form-data; name="submit_btn"

发送
------WebKitFormBoundaryDjMAMsD95W3eYF1i--
复制代码

上面这些信息都是点击发送时,请求里面的body,大体看了下,以前分析的时候,少了captcha-id这个参数,猜想这是就是关键;

简单尝试了下,发现这个captcha-id每次都会不同,并且从请求里面也看不出啥,既然这个ID可能跟验证码有绑定关系,那咱们就解析下这个网页的结构,看下能不能找到这个字段?

点击F12,定位到验证码这块:

看了下右侧的属性,好像没有什么问题,想一想,既然不是图片,又要绑定ID属性,那可能就是输入验证码那里了,继续看~

必定位到输入验证码的框里面,嘤嘤嘤,看发现了啥,这不就是想要的captcha-id吗?

爱是怀疑,那咱们就来用postman验证一下吧;

页面刷新下:

哈哈哈哈,能够了,再次嘤嘤嘤~

那如今的逻辑,应该是修改为这样:

1)打开帖子页面,判断是否须要输入验证码,若是须要,获取captcha-id跟验证码,
而后post请求captcha-id和验证码
2)若是不须要输入验证码,那就直接post请求便可
复制代码

既然如此,对比下须要输入验证码跟不须要验证码时的网页结构吧;

须要验证码:

不须要验证码:

对比可知,须要输入验证码的时候,会多了一个div标签,这个div标签展开了,二维码的下载连接也能找到,captcha-id也能找到,既然如此,使用xpath就能判断了,判断captcha_image,若是能获取到,就是须要验证码;

简单作了下实验,看看能不能获取到这个captcha,有如下代码:

import requests
from lxml import html
response = requests.get(db_url).content
selector = html.fromstring(response)
captcha = selector.xpath("//img[@id=\"captcha_image\"]/@src")
print(captcha)

可是结果返回的是[],意思就是没有获取到这个值
复制代码

把response打印出来,真的是没有这个值,这里且慢,一开始JB的想法是,没有这个值,就说明这块数据是JS生成的,那咱们研究看下怎么获取JS生成的网页数据,而后就霹雳吧啦的介绍selenium;

事实证实,并不须要那么复杂(浪费了半天时间了。。),仍是上面这串代码:

import requests
from lxml import html
response = requests.get(db_url,verify=False).content
print(response)
复制代码

把response打印出来,结果发现内容长这样:

嗯,认真点看,发现都是编码过的,可是JB一开始并无细看,一看获取是空的,就认定是JS加载的,其余这里,只须要改为这样就行了:

response = requests.get(db_url,verify=False).content.decode()
复制代码

效果图,这样就能看到中文了:

这个解决方案,花了半天无心发现的,算是get 到一个小点了,之后request后必定要decode,不用还用selenium就跑远了;

获取二维码下载地址

按照下面的内容,就能够写出这样的代码:

response = requests.post(db_url,headers=headers, data=params,verify=False).content.decode()
selector = html.fromstring(response)
captcha = selector.xpath("//img[@id=\"captcha_image\"]/@src")
print(captcha)
复制代码

而后再想须要回复的帖子回复三次,让验证码出现,而后再执行这个脚本,否则验证码不出现,就会获取为[]的:

这样,就能获取到验证码图片啦,按照上面说的,若是是有验证码,就获取图片连接跟验证码ID,若是没有,则直接post请求,所以不难写出下面的代码;

import requests
from apscheduler.schedulers.blocking import BlockingScheduler
from lxml import html

# 豆瓣具体帖子连接
db_url = "https://www.douban.com/note/657346123/"
# 豆瓣具体帖子回复的接口,格式是帖子连接+/add_comment
db_url_commet = "https://www.douban.com/note/657346123///add_comment"

scheduler = BlockingScheduler()

headers = {
    "Host": "www.douban.com",
    "Referer": "https://www.douban.com/group/topic/121989778/?start=0",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
    "Cookie":"your cookie"   #这里须要输入你本身的cookie信息,若是遇到转义字符,转
        义字符前面加\便可
}

params = {
    "ck": "TXEg",
    "rv_comment": "jbtest11111",
}

def my_job():
    # 获取网页信息
    response = requests.post(db_url, headers=headers, data=params, verify=False).content.decode()
    selector = html.fromstring(response)
    captcha_image = selector.xpath("//img[@id=\"captcha_image\"]/@src")
    if(captcha_image):
        print(captcha_image)
        captcha_id = selector.xpath("//input[@name=\"captcha-id\"]/@value")
        print(captcha_id)
    else:
        # 发起请求请求
        requests.post(db_url_commet, headers=headers, data=params, verify=False)

# 每隔10S就请求一次
scheduler.add_job(my_job, "interval", seconds=2, id="db")
scheduler.start()
复制代码

效果图:

Ok,这样就能获取到二维码图片跟图片对应的ID了,那接下来要干吗?

识别二维码

既然能获取二维码图片,而请求的时候又要带上这个字段,那就意味着,必须先下载这个图片,而后去识别这种图片,而后放到请求上一块儿提交;

下载图片&命名:

import requests
import re
i = "https://www.douban.com/misc/captcha?id=9iGoXeJXeos3E1JukgkltEVp:en&size=s"
captcha_name = re.findall("id=(.*?):",i)   #findall返回的是一个列表
filename = "douban_%s.jpg" % (str(captcha_name[0]))
print("文件名为:"+filename)
#建立文件名
with open(filename, 'wb') as f:
#以二进制写入的模式在本地构建新文件
    header = {
        'User-Agent': '"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",'
        ,'Referer': i}
    f.write(requests.get(i,headers=header).content)
    print("%s下载完成" % filename)
# urllib.request.urlretrieve(requests.get(i,headers=header), "%s%s%s.jpg" % 
(dir, image_title, num))
复制代码

ok,此时图片下载完成了,好比上面下载的这张,是这样的验证码:

tesserocr

那咱们要识别它,就先试试tesserocr的识别率如何,有关tesserocr的文章,请点击这里了解下,里面有详细简介,这里不重复说明:

import tesserocr
from PIL import Image

#新建Image对象
image = Image.open("5.jpg")
#进行置灰处理
image = image.convert('L')
#这个是二值化阈值
threshold = 4
table = []

for i in  range(256):
    if i < threshold:
        table.append(0)
    else:
        table.append(1)
#经过表格转换成二进制图片,1的做用是白色,否则就所有黑色了
image = image.point(table,"1")
image.show()
#调用tesserocr的image_to_text()方法,传入image对象完成识别
result = tesserocr.image_to_text(image)
print(result)
复制代码

通过屡次调试处理,发现把二值化阈值调到4,是最优的效果,二值化后的验证码长这样:

而代码识别的结果:

啧啧啧,这样定制的二值化都不行,就再也不尝试了,否则每一个图片都这么定制化去作,还得作?

百度OCR

既然tesserocr效果很差,那就试试百度的OCR吧,(有关百度OCR的文章,请点击这里查看):

from aip import AipOcr
from PIL import Image
import os

""" 你的 APPID AK SK """
config = {
    "appId": '',
    "apiKey":'',
    "secretKey":''
}

client = AipOcr(**config)

""" 读取图片 """
def get_file_content(filePath):
    with open(filePath, 'rb') as fp:
        return fp.read()

def get_image_str(image_path):
    image = get_file_content(image_path)
    """ 调用通用文字识别, 图片参数为本地图片 """
    result = client.basicAccurate(image)

    #结果拼接返回输出s
    if 'words_result' in result:
        return ''.join([w['words'] for w in result['words_result']])

if __name__ == "__main__":
    print(get_image_str("5.jpg"))
复制代码

直接运行后的结果:

啧啧啧,果真是BAT,果真是高精准的,能识别呢,那咱们继续试试不一样验证码,结果发现,下面这张运行后,什么都识别不出来(看来也不是万能的);

既然如此,那咱们把刚刚tesserocr的二值化处理放到这里,会不会有效果?代码以下:

""" 读取图片 """
def get_file_content(filePath):
    # 新建Image对象
    image = Image.open(filePath)
    # 进行置灰处理
    image = image.convert('L')
    threshold = 15
    table = []

    for i in range(256):
        if i < threshold:
            table.append(0)
        else:
            table.append(1)
    # 经过表格转换成二进制图片,1的做用是白色,否则就所有黑色了
    image = image.point(table, "1")

    image.save(os.path.join(os.getcwd(), os.path.basename(filePath)))

    with open(filePath, 'rb') as fp:
        return fp.read()
复制代码

执行后发现,识别率仍是感人,其实以前也测试过,这种验证码的确存在部分记录失败的状况:

既然百度(免费)的都不行,那咱们就换个收费吧,收费的打码平台,数超级鹰名气比较大了;

超级鹰

官网地址:www.chaojiying.com/
打开官网,有个免费测试,点击后发现要登陆,那就先注册了;
结果发现那个免费测试仍是要题分,要关注公众号绑定帐号才送1000题分,这个就是免费测试,懒得折腾,直接充钱吧;

超级鹰是按量级收费,量大便宜,标准价格:1元=1000题分,不一样验证码类型,须要的题分不同,详情能够到这里查询:www.chaojiying.com/price.html#

充值后,返回到刚刚那个免费测试界面进行测试

等待一会,网页就会有弹窗:

上传的验证码以下:

对比下,结果彻底正确,收费的果真牛逼,这个也是连百度的都搞不定的;

那咱们换一种微博的验证码:

啧啧啧,无难度啊;

超级鹰也支持接入,首页底部有个api文档说明,点击进去发现支持各类语言,找到Python,把demo下载下来,api文档连接:www.chaojiying.com/api-14.html

源码是基于2.X写的,不难看懂,可是网上有同窗从新整理下,简洁不少,就把这个代码贴出来吧,做者:coder-pig:

from hashlib import md5
import requests
# 超级鹰参数
cjy_params = {
    'user': '448975523',
    'pass2': md5('你的密码'.encode('utf8')).hexdigest(),
    'softid': '96001',
}
# 超级鹰请求头
cjy_headers = {
    'Connection': 'Keep-Alive',
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
# 超级鹰识别验证码
def cjy_fetch_code(im, codetype):
    cjy_params.update({'codetype': codetype})
    files = {'userfile': ('ccc.jpg', im)}
    resp = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=cjy_params, files=files,
                  headers=cjy_headers).json()
    # 错误处理
    if resp.get('err_no', 0) == 0:
        return resp.get('pic_str')

# 调用代码
if __name__ == '__main__':
    im = open('captcha.jpg', 'rb').read()
    print(cjy_fetch_code(im, 1902))
复制代码

执行后,发现验证码是对的,同时,一次请求大概是3S左右;

ok,验证码破解这块就到这里了,想接入超级鹰、百度,tesserocr,又或者是本身写我的工智能算法,任君选择;

豆瓣自动回复功能-终

为了效果着想,这里采用了超级鹰来破解,先看看log:

从log看到,无论是有没有验证码的状况,都是能正常评论的,那去豆瓣看看是否是真成功了?

从豆瓣记录上看,嗯,破解了,good;

上面整合下就是全部的代码了;

还能够有的小优化

是否是这样就完了?
非也,由于如今咱们用的是收费的,因此验证码的准确率由超级鹰担保了,可是假如用免费的,识别率感人,从用户角度,会关心哪些验证码失败,但愿有个通知,此时就能够接入server酱,一旦验证码验证失败就微信通知,效果以下:

感兴趣的能够去server酱官网了解下:sc.ftqq.com/3.version

18.9.13更新

今天执行脚本发现,脚本没有报错,可是呢,执行后不会生效(对应帖子没有相关评论,并且也不会出现验证码),jb,一脸懵逼,而后用postman尝试也不行,后来经过网页模拟及postman一步步模拟后,发现问题根源是,请求的时候,body的ck字段的内容是会变化的,以前默认是写着:

"ck": "TXEg"
复制代码

也许有同窗好奇何时会变化,经测试发现,当用户退出登陆后,这个字段就会变化,而对于脚原本说,确定不会出去啦,那就转化成当cookie失效时,这个字段的内容会发生变化;

而cookie失效的解决方案也是用的,用selenium每次登陆获取cookie,这样就不会存在cookie失效的状况了,但这里不介绍这点,后面在写selenium的时候再介绍;

这里会简单介绍下ck的值怎么获取的;

在网页上模拟评论,而后能得到ck的值,而后复制,去到网页的HTML搜索,就会出现这个值是怎么获取的了;

image_1cn9pl0jdpteq9clbd1phi1sr9vu.png-29.7kB

没错,就是退出按钮url的最后4个字母,这里的话,用xpath获取到这个url,再获取这4个字母就行了;

因而乎写了个xpath

selector.xpath("//div/ul/li/div/table/tbody/tr/td/a/@href")
复制代码

结果发现,怎么拿都拿不到,后来response.content的内容输出看了下,的确没发现这个退出按钮的代码;

这就意味着,这块是JS生成的,想获取JS生成后的HTML,目前只能用selenium,可是这里还有个问题,selenium想拼接一个自定义的cookie是很是很是的麻烦,以前折腾好久都不行,因此这条路就放弃了;

既然selenium不期望了,那是否是还有其余法子? 因此就拿ck这个key一路去找,结果发现cookie上有这个值:

微信图片_20180914100702.png-597kB

这里面有3个值,第一个就是以前代码hardcore的内容,对比了下,咱们要的值是最后一个ck的value,也作了几个从新登陆的操做,若是cookie最后一个ck值变的话,那这个退出按钮的ck值也会跟着变,抛开cookie过时的想法,ck这个就直接获取cookie的最后一个ck内容;

#获取ck对应的value,经过cookie获取最后一个ck的值
def get_ck():
    # 这个废了,不能用selenium
    # ck_value = selector.xpath("//div/ul/li/div/table/tbody/tr/td/a/@href")
    text = re.findall("ck=(.*?);",headers["Cookie"])[-1]
    return text
复制代码

久违的自动顶贴又回来了

image_1cnauiiq25pumns1ldhau111q11g.png-56.8kB

小结

呼,奋斗到4点,终于撸完了,其实自动回帖功能很简单,周一下午看到,当天晚上就搞定了,而后在折腾验证码的问题,包括想尝试调优,不用收费的,结果折腾好几晚读不行,
再而后就是response的html代码问题,一开始觉得是js加载的,结果发现是须要decode,跟JS一点关系都没有,其中还有一晚是整理selenium的知识,由于当时认为跟JS有关系,就搞这玩意,后面会看一本selenium的事,再整一篇selenium介绍文字吧;

回到文章,本文介绍的内容比较多,大概分为3块以下:
1)如何分析豆瓣评论接口;
2)介绍python定时任务框架APScheduler
3)验证码破解(tesserocr、百度OCR、超级鹰)

其实也没什么特别好讲的,很简单的东西,只是繁琐而已,之因此花那么多时间,是思惟被扩散了,就这样吧,谢谢你们;

相关文章
相关标签/搜索