全文加上代碼總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是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
複製代碼
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=[[])
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")
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次,就必定會彈出這個驗證碼進行驗證;
按照一開始的套路,那咱們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的文章,請點擊這裏瞭解下,裏面有詳細簡介,這裏不重複說明:
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,是最優的效果,二值化後的驗證碼長這樣:
而代碼識別的結果:嘖嘖嘖,這樣定製的二值化都不行,就再也不嘗試了,否則每一個圖片都這麼定製化去作,還得作?
既然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
今天執行腳本發現,腳本沒有報錯,可是呢,執行後不會生效(對應帖子沒有相關評論,並且也不會出現驗證碼),jb,一臉懵逼,而後用postman嘗試也不行,後來經過網頁模擬及postman一步步模擬後,發現問題根源是,請求的時候,body的ck字段的內容是會變化的,以前默認是寫着:
"ck": "TXEg"
複製代碼
也許有同窗好奇何時會變化,經測試發現,當用戶退出登陸後,這個字段就會變化,而對於腳原本說,確定不會出去啦,那就轉化成當cookie失效時,這個字段的內容會發生變化;
而cookie失效的解決方案也是用的,用selenium每次登陸獲取cookie,這樣就不會存在cookie失效的狀況了,但這裏不介紹這點,後面在寫selenium的時候再介紹;
這裏會簡單介紹下ck的值怎麼獲取的;
在網頁上模擬評論,而後能得到ck的值,而後複製,去到網頁的HTML搜索,就會出現這個值是怎麼獲取的了;
沒錯,就是退出按鈕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上有這個值:
這裏面有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
複製代碼
久違的自動頂貼又回來了
呼,奮鬥到4點,終於擼完了,其實自動回帖功能很簡單,週一下午看到,當天晚上就搞定了,而後在折騰驗證碼的問題,包括想嘗試調優,不用收費的,結果折騰好幾晚讀不行,
再而後就是response的html代碼問題,一開始覺得是js加載的,結果發現是須要decode,跟JS一點關係都沒有,其中還有一晚是整理selenium的知識,由於當時認爲跟JS有關係,就搞這玩意,後面會看一本selenium的事,再整一篇selenium介紹文字吧;
回到文章,本文介紹的內容比較多,大概分爲3塊以下:
1)如何分析豆瓣評論接口;
2)介紹python定時任務框架APScheduler
3)驗證碼破解(tesserocr、百度OCR、超級鷹)
其實也沒什麼特別好講的,很簡單的東西,只是繁瑣而已,之因此花那麼多時間,是思惟被擴散了,就這樣吧,謝謝你們;