JB的Python之旅-豆瓣自動頂貼功能

jb發表於2018-09-01

溫馨小提示

全文加上程式碼總6.8k個字,閱讀大約10分鐘,謝謝你的點選,願能解決你的問題;

前言

前幾天在小豬群裡,有同學問,有人知道怎麼做豆瓣自動回覆功能嗎?然後群裡就各種大神出馬相助,各種填程式碼給資料的,也有同學說用selenium模擬下就好了等等,其實大家都說的對,伸手黨固然不好,但是考慮到讓一個不瞭解的同學去做這個事,的確有門檻,更別說查資料用selenium了;

JB的Python之旅-豆瓣自動頂貼功能

豆瓣回覆功能嘗試

一開始的想法,也是用selenium的,但是想著,還是先模擬下,看看豆瓣的回覆流程吧;
先開啟需要回復的帖子,然後接著登入豆瓣,然後回到剛剛這個帖子上,觀察下介面,回覆按鈕就在底部,點選傳送就是評論了;

JB的Python之旅-豆瓣自動頂貼功能

那我們就抓包看下請求吧,瀏覽器按F12,選擇network,點選左邊紅色按鈕兩次,把之前的資料都清除;

JB的Python之旅-豆瓣自動頂貼功能

接著就輸入內容,點選傳送按鈕,然後檢視network介面,不難找到傳送評論的請求,從名字上看,也能確認是傳送評論的請求;

JB的Python之旅-豆瓣自動頂貼功能
然後看了下右邊的請求資料,不難發現介面地址是:

https://www.douban.com/group/topic/121989778/add_comment
複製程式碼

而且發現,請求的時候,要帶4個引數:

ck=TXEg
rv_comment=層主真帥,贊贊贊  #這個就是要回復的內容
start=0
submit_btn=傳送
複製程式碼

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

請求頭引數用了常規的cookie、user-agent、referer、host;

JB的Python之旅-豆瓣自動頂貼功能

而body這塊,雖然上面抓包看到有4個引數,但是實際驗證只需要2個即可,傳送的內容就是jbtest;

JB的Python之旅-豆瓣自動頂貼功能

那在postman上點選send,然後在那個帖子上重新整理下頁面,居然能看到剛剛回復的內容

JB的Python之旅-豆瓣自動頂貼功能

看來,豆瓣回覆功能只需要調介面就行了,都不用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引數,像豆瓣的評論介面發一個請求即可,執行下指令碼,重新整理下網頁:

JB的Python之旅-豆瓣自動頂貼功能

沒問題,回覆功能多簡單,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()
複製程式碼

執行後的效果:

JB的Python之旅-豆瓣自動頂貼功能

很簡單有沒有,先初始化,然後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)如果不想具體的時間,而是某個範圍的話:

JB的Python之旅-豆瓣自動頂貼功能

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()
複製程式碼

效果如下:

JB的Python之旅-豆瓣自動頂貼功能

的確是每隔10S傳送一次,good~

做到這裡,你以為完事了?
嗯,怎麼說呢,如果是一個帖子的話,是完事了,但是如下是以下2種場景之一,還需要折騰:

1)同一個帖子,需要短時間內回覆,這個短時間沒法定義,可能
幾分鐘都算,除非是像正常使用者幾個小時回覆一次就可能沒問題;
2)N個帖子都要回復,而且回覆間隔比較短,類似問題1;
複製程式碼

那這兩種情況,會導致什麼問題?當然是觸發豆瓣的防爬蟲啦:

JB的Python之旅-豆瓣自動頂貼功能

目前發現,每次評論就算相隔1分鐘,只要滿3次,就一定會彈出這個驗證碼進行驗證;

獲取驗證碼ID

按照一開始的套路,那我們F12看下輸入驗證碼後發起請求的引數:

JB的Python之旅-豆瓣自動頂貼功能
發現會在請求的帶上一個叫captcha-solution欄位,value就是驗證碼內容,那我們就異想天開的試試,用postman在body上加上這個驗證碼引數值,看能否評論?

驗證碼資訊:

JB的Python之旅-豆瓣自動頂貼功能

postman請求內容:

JB的Python之旅-豆瓣自動頂貼功能

點選send,然後原來的網頁重新整理一下,結果如下:

JB的Python之旅-豆瓣自動頂貼功能

對的,內容沒有變,因為這樣肯定是不生效的,哪有那麼容易;

驗證碼場景:

現在有網址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,定位到驗證碼這塊:

JB的Python之旅-豆瓣自動頂貼功能
看了下右側的屬性,好像沒有什麼問題,想想,既然不是圖片,又要繫結ID屬性,那可能就是輸入驗證碼那裡了,繼續看~

JB的Python之旅-豆瓣自動頂貼功能

一定位到輸入驗證碼的框裡面,嚶嚶嚶,看發現了啥,這不就是想要的captcha-id嗎?

JB的Python之旅-豆瓣自動頂貼功能

愛是懷疑,那我們就來用postman驗證一下吧;

JB的Python之旅-豆瓣自動頂貼功能
頁面重新整理下:

JB的Python之旅-豆瓣自動頂貼功能

哈哈哈哈,可以了,再次嚶嚶嚶~

JB的Python之旅-豆瓣自動頂貼功能

那現在的邏輯,應該是修改成這樣:

1)開啟帖子頁面,判斷是否需要輸入驗證碼,如果需要,獲取captcha-id跟驗證碼,
然後post請求captcha-id和驗證碼
2)如果不需要輸入驗證碼,那就直接post請求即可
複製程式碼

既然如此,對比下需要輸入驗證碼跟不需要驗證碼時的網頁結構吧;

需要驗證碼:

JB的Python之旅-豆瓣自動頂貼功能

不需要驗證碼:

JB的Python之旅-豆瓣自動頂貼功能

對比可知,需要輸入驗證碼的時候,會多了一個div標籤,這個div標籤展開了,二維碼的下載連結也能找到,captcha-id也能找到,既然如此,使用xpath就能判斷了,判斷captcha_image,如果能獲取到,就是需要驗證碼;

JB的Python之旅-豆瓣自動頂貼功能

簡單做了下實驗,看看能不能獲取到這個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的Python之旅-豆瓣自動頂貼功能
嗯,認真點看,發現都是編碼過的,但是JB一開始並沒有細看,一看獲取是空的,就認定是JS載入的,其他這裡,只需要改成這樣就好了:

response = requests.get(db_url,verify=False).content.decode()
複製程式碼

效果圖,這樣就能看到中文了:

JB的Python之旅-豆瓣自動頂貼功能

這個解決方案,花了半天無意發現的,算是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)
複製程式碼

然後再想需要回復的帖子回覆三次,讓驗證碼出現,然後再執行這個指令碼,不然驗證碼不出現,就會獲取為[]的:

JB的Python之旅-豆瓣自動頂貼功能

這樣,就能獲取到驗證碼圖片啦,按照上面說的,如果是有驗證碼,就獲取圖片連結跟驗證碼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()
複製程式碼

效果圖:

JB的Python之旅-豆瓣自動頂貼功能

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,此時圖片下載完成了,比如上面下載的這張,是這樣的驗證碼:

JB的Python之旅-豆瓣自動頂貼功能

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,是最優的效果,二值化後的驗證碼長這樣:

JB的Python之旅-豆瓣自動頂貼功能
而程式碼識別的結果:

JB的Python之旅-豆瓣自動頂貼功能

嘖嘖嘖,這樣定製的二值化都不行,就不再嘗試了,不然每個圖片都這麼定製化去做,還得做?

百度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"))
複製程式碼

直接執行後的結果:

JB的Python之旅-豆瓣自動頂貼功能

嘖嘖嘖,果然是BAT,果然是高精準的,能識別呢,那我們繼續試試不同驗證碼,結果發現,下面這張執行後,什麼都識別不出來(看來也不是萬能的);

JB的Python之旅-豆瓣自動頂貼功能

既然如此,那我們把剛剛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()
複製程式碼

執行後發現,識別率還是感人,其實之前也測試過,這種驗證碼的確存在部分記錄失敗的情況:

JB的Python之旅-豆瓣自動頂貼功能

既然百度(免費)的都不行,那我們就換個收費吧,收費的打碼平臺,數超級鷹名氣比較大了;

超級鷹

官網地址:www.chaojiying.com/
開啟官網,有個免費測試,點選後發現要登入,那就先註冊了;
結果發現那個免費測試還是要題分,要關注公眾號繫結賬號才送1000題分,這個就是免費測試,懶得折騰,直接充錢吧;

超級鷹是按量級收費,量大便宜,標準價格:1元=1000題分,不同驗證碼型別,需要的題分不一樣,詳情可以到這裡查詢:www.chaojiying.com/price.html#

充值後,返回到剛剛那個免費測試介面進行測試

JB的Python之旅-豆瓣自動頂貼功能

等待一會,網頁就會有彈窗:

JB的Python之旅-豆瓣自動頂貼功能

上傳的驗證碼如下:

JB的Python之旅-豆瓣自動頂貼功能

對比下,結果完全正確,收費的果然牛逼,這個也是連百度的都搞不定的;

那我們換一種微博的驗證碼:

JB的Python之旅-豆瓣自動頂貼功能
JB的Python之旅-豆瓣自動頂貼功能

嘖嘖嘖,無難度啊;

JB的Python之旅-豆瓣自動頂貼功能

超級鷹也支援接入,首頁底部有個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左右;

JB的Python之旅-豆瓣自動頂貼功能

ok,驗證碼破解這塊就到這裡了,想接入超級鷹、百度,tesserocr,又或者是自己寫個人工智慧演算法,任君選擇;

豆瓣自動回覆功能-終

為了效果著想,這裡採用了超級鷹來破解,先看看log:

JB的Python之旅-豆瓣自動頂貼功能
從log看到,不管是有沒有驗證碼的情況,都是能正常評論的,那去豆瓣看看是不是真成功了?

JB的Python之旅-豆瓣自動頂貼功能

從豆瓣記錄上看,嗯,破解了,good;

上面整合下就是所有的程式碼了;

還可以有的小優化

是不是這樣就完了?
非也,因為現在我們用的是收費的,所以驗證碼的準確率由超級鷹擔保了,但是假如用免費的,識別率感人,從使用者角度,會關心哪些驗證碼失敗,希望有個通知,此時就可以接入server醬,一旦驗證碼驗證失敗就微信通知,效果如下:

JB的Python之旅-豆瓣自動頂貼功能

感興趣的可以去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、超級鷹)

其實也沒什麼特別好講的,很簡單的東西,只是繁瑣而已,之所以花那麼多時間,是思維被擴散了,就這樣吧,謝謝大家;

JB的Python之旅-豆瓣自動頂貼功能

相關文章