實(shí)現(xiàn)后的結(jié)果
可以看到使用我本人的用戶名和密碼登錄成功了腕侄,驗(yàn)證碼也驗(yàn)證成功了乍惊。
前因
前段時(shí)間看到了py,就準(zhǔn)備學(xué)習(xí)學(xué)習(xí)锰霜,但是學(xué)習(xí)的是py3,網(wǎng)絡(luò)上很多練手的項(xiàng)目都是py2寫的蚓挤,很多在3是跑不起來思犁,所以就自己搞了搞py3的12306自動(dòng)登錄互墓,在我前面的文章還寫了查詢某天的車票情況必尼。
為什么要寫12306呢?
首先是前面項(xiàng)目查票的前綴篡撵,其次就是在網(wǎng)絡(luò)上看到了某個(gè)學(xué)院有個(gè)老師講12306判莉,但是是收費(fèi)課程,當(dāng)時(shí)我問他育谬,能不能提示一下自動(dòng)登錄所使用到的技術(shù)券盅,但是他回答我他要講一個(gè)月才能講完,然后就沒有下文了膛檀。這個(gè)回答明顯的就是敷衍锰镀。但是我并沒有生氣,對(duì)于處在互聯(lián)網(wǎng)時(shí)代的我們咖刃,并沒有什么東西不能自己學(xué)習(xí)的(夸張了)泳炉,但是確實(shí)是很多資源都可以找到。在這里吐槽一句嚎杨,在國內(nèi)的資源問題真的很嚴(yán)重花鹅,很多人都是不愿意教別人。不知道各位有沒有遇到這種情況枫浙。
我所經(jīng)歷的事兒
在動(dòng)手做這個(gè)項(xiàng)目的時(shí)候刨肃,我了解人工智能,機(jī)器學(xué)習(xí)箩帚,深度學(xué)習(xí)(deep learning)真友,但是由于時(shí)間,個(gè)人能力的問題紧帕,目前還不打算基礎(chǔ)深度學(xué)習(xí)和機(jī)器學(xué)習(xí)盔然。但是我所做的這個(gè)項(xiàng)目又必須用到深度學(xué)習(xí)的知識(shí),所以我選擇了google treeseract 和百度的圖像識(shí)別技術(shù)來幫助我完成這個(gè)項(xiàng)目是嗜。
第一次標(biāo)題識(shí)別使用的是google的開源項(xiàng)目愈案,但是效率很低很低,可能是中文支持問題叠纷,還有就是12306字體問題刻帚。
第二次使用百度文字識(shí)別技術(shù),效率不錯(cuò)涩嚣,是不是給李彥宏打廣告了崇众。
然后就是識(shí)別八張小圖片的問題掂僵,該問題受到了fuck12306項(xiàng)目的幫助,在調(diào)用百度識(shí)圖接口上顷歌。目前百度的接口上傳文件應(yīng)該是有變化锰蓬。
在開發(fā)中最郁悶的就是,正在嘗試識(shí)別登錄眯漩,在有一天下午我突然發(fā)現(xiàn),mmp以前是只有一個(gè)類型(只有一個(gè)類型的選擇)芹扭,現(xiàn)在穿插了兩種出來,有可能訂書機(jī)赦抖,后面在跟一個(gè)其他的舱卡。這就讓我郁悶的很,說明我的代碼是需要更改來提高比對(duì)效率队萤。說改就改轮锥,擼起袖子就開干。
代碼那點(diǎn)事兒
現(xiàn)在聊聊代碼那點(diǎn)事兒要尔,程序猿不就是一個(gè)賣字母為生的人么舍杜?至少我就是這樣的,分享一個(gè)我遇到的事情赵辕,有個(gè)公司周六叫員工去幫忙搬一下桌子既绩,改變一個(gè)辦公室布局,這是一個(gè)小事情还惠,幫個(gè)忙而已饲握。但是那個(gè)頭說的是,周六去公司搬桌子吸重,然后下午公司聚餐互拾,沒去搬桌子的就沒有歪今,她好定位子嚎幸,這都讓人忍了,最后還要請(qǐng)假寄猩,請(qǐng)假嫉晶,驚不驚喜,意不意外田篇。毀三觀的公司替废。
好吧,我多嘴了泊柬,開始我們簡(jiǎn)單的代碼講解椎镣。
庫
- request http請(qǐng)求
- pillow 圖形處理
- baidu-aip 百度文字識(shí)別
由著三個(gè)庫搞定,還有py的庫就不說了兽赁。自帶的技能状答。
開發(fā)思路
- 下載驗(yàn)證碼圖片
- 切圖冷守,識(shí)別標(biāo)題文字
- 切圖,分別識(shí)別八張小圖片
3.1 識(shí)別內(nèi)容惊科,沒有最佳內(nèi)容再次識(shí)別相似內(nèi)容拍摇,提高對(duì)比度(自己發(fā)明的) - 進(jìn)行內(nèi)容比對(duì),找到坐標(biāo)點(diǎn),了解過這種形式的驗(yàn)證碼的都知道這種是坐標(biāo)驗(yàn)證碼馆截,不明白的童鞋自行百度充活。
- 嘗試登陸
- 搞定
賣字母咯
語調(diào)請(qǐng)讀成安琪拉的的語調(diào),因?yàn)槲易罱谕孓r(nóng)藥蜡娶。
對(duì)了混卵,下面的代碼都是部分代碼,完整代碼在文尾github上窖张。
下載驗(yàn)證碼
def get_picture(get_pic_url, img_code):
response = req.get(get_pic_url, headers=headers, verify=False)
response.encoding = 'utf-8'
if response.status_code == 200:
with open(img_code, "wb") as f:
f.write(response.content)
print("圖片下載成功")
return True
else:
print("圖片下載失敗淮菠,正在重試....")
get_picture(get_pic_url, img_code)
這就把驗(yàn)證碼圖片保存起來了。
識(shí)別標(biāo)題
識(shí)別標(biāo)題得說一下百度的文字識(shí)別api荤堪,這是一個(gè)百度應(yīng)用吧合陵,我上一篇簡(jiǎn)書專門講了這個(gè),傳送門,怎么申請(qǐng)key澄阳,使用等等拥知,代碼也托管了的。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from aip import AipOcr
__author__ = "雷洪飛"
"""
利用百度圖像識(shí)別api做文字識(shí)別碎赢,目的是為了做12306的圖片校驗(yàn)低剔。
"""
# 定義常量
APP_ID = '10508877'
API_KEY = 'kpFtUgtOaxmKkNa2C0x7Q7mN'
SECRET_KEY = 'TTX5ginIXZyfGtdH8UTO4kF5M41lf3fb '
# 初始化AipFace對(duì)象
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
# 定義參數(shù)變量
options = {
'detect_direction': 'true',
'language_type': 'CHN_ENG',
}
class BaiDu(object):
# 獲取圖片
def get_file_content(self, file_path):
"""獲取圖片數(shù)據(jù)"""
with open(file_path, 'rb') as fp:
return fp.read()
def get_result(self, image_url):
"""
識(shí)別結(jié)構(gòu)
:return: 返回識(shí)別結(jié)果
"""
image = self.get_file_content(image_url)
return client.basicGeneral(image, options)
if __name__ == "__main__":
# 獲取圖片
baidu = BaiDu()
# 得到識(shí)別結(jié)果
result = baidu.get_result("../images/code.png")
# 輸出識(shí)別結(jié)果
print(result['words_result'][0]['words'])
完整代碼都在這里,我將他封裝成了對(duì)象肮塞,方面各個(gè)模塊的調(diào)用襟齿。
識(shí)別八張圖片內(nèi)容
這個(gè)算是比較大的工程了,需要切圖枕赵,識(shí)別猜欺,提取識(shí)別內(nèi)容,保存坐標(biāo)點(diǎn)拷窜,返回內(nèi)容...
文字走一走:
使用百度圖片搜索功能進(jìn)行識(shí)別开皿,百度有一個(gè)上傳圖片進(jìn)行百度的功能,google也有的。沒用過的趕緊去試試篮昧,這就是用到了深度學(xué)習(xí)的知識(shí)赋荆,神經(jīng)網(wǎng)絡(luò)xxx,噼里啪啦一大堆。關(guān)注人工智能的童鞋就知道Ng(吳恩達(dá)大神)以前就是在百度懊昨。
百度的識(shí)別圖片是分為兩步走:
-
需要上傳圖片或者使用圖片uri進(jìn)行識(shí)別的窄潭,所以就需要上傳,我下載了百度的源代碼分析酵颁。最后也沒上傳成功嫉你。py不行信认。所以就找了fuck12306的上傳方式來進(jìn)行上傳的。百度現(xiàn)在使用https,http兩個(gè)不同的上傳地址均抽,最后都是返回一個(gè)包含圖片地址的json格式嫁赏。我們就需要圖片地址,大小等油挥。潦蝇。
百度上傳返回結(jié)果 帶上上傳了圖片的地址,大小等信息進(jìn)行深度學(xué)習(xí)深寥。然后染回結(jié)果攘乒。
剛才上傳地地址,大小等信息就被編碼到url地址中了惋鹅。
這個(gè)接口返回的結(jié)果就是我們的所識(shí)別圖片的結(jié)果则酝,是以網(wǎng)頁的形式返回。
但是會(huì)出現(xiàn)圖片搜索不到東西的情況下闰集,那么百度javascript將會(huì)再次發(fā)送一個(gè)請(qǐng)求沽讹,查詢相似結(jié)果。
https://image.baidu.com/n/similar?queryImageUrl=http%3A%2F%2Fc.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2Fa8014c086e061d95802389b570f40ad163d9cad2.jpg&querySign=1842048230,1720327039&word=&querytype=0&t=1513414228966&rn=60&sort=&fr=pc&pn=
這個(gè)就是返回的相似圖片的接口武鲁,返回的是json格式爽雄。
相似圖片接口內(nèi)容.png
篇幅有限,截圖就小點(diǎn)了沐鼠。
我會(huì)獲取其中的一個(gè)title屬性挚瘟。那就是我們鼠標(biāo)放到圖片上面顯示的文字。用來比對(duì)驗(yàn)證碼標(biāo)題問題的饲梭。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import urllib3
from PIL import Image, ImageFilter, ImageEnhance
from bs4 import BeautifulSoup
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
__author__ = "雷洪飛"
class RecoginitionContainer(object):
def __init__(self, base_img_url):
self.base_img_url = base_img_url
url = "http://image.baidu.com/pictureup/uploadshitu?fr=flash&fm=index&pos=upload"
headers = {
"User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"}
def __upload_pic__(slef, img, filex, url):
"""上傳圖片乘盖,得到圖片地址"""
img.save(slef.base_img_url + "/query_temp_img" + filex + ".png")
raw = open(slef.base_img_url + "/query_temp_img" + filex + ".png", 'rb').read()
files = {
'fileheight': "0",
'newfilesize': str(len(raw)),
'compresstime': "0",
'Filename': "image.png",
'filewidth': "0",
'filesize': str(len(raw)),
'filetype': 'image/png',
'Upload': "Submit Query",
'filedata': ("image.png", raw)
}
resp = requests.post(url, files=files, headers=slef.headers, verify=False)
redirect_url = "http://image.baidu.com" + resp.text
return redirect_url
def __get_query_content__(slef, query_url):
response = requests.get(query_url, headers=slef.headers, verify=False)
li = list()
if response.status_code == requests.codes.ok:
bs = BeautifulSoup(response.text, "html.parser")
re = bs.find_all("a", class_="guess-info-word-link guess-info-word-highlight")
for link in re:
li.append(link.get_text())
# print(link.get_text())
# 再次獲取圖中動(dòng)物可能是
te = bs.find_all("ul", class_="shituplant-tag")
for l in te:
# 得到有可能包含的數(shù)據(jù)
for child in l.children:
try:
li.append(child.get_text())
except:
pass
# 再次獲取相似圖片,其中在有一個(gè)圖片描述憔涉,進(jìn)行獲取就知道,重新掉了一個(gè)接口
# pc_search 替換成similar在搜索一次
if len(li) == 0:
once_url = query_url.replace("pc_search", "similar")
resp = requests.get(once_url, headers=slef.headers, verify=False)
js = resp.json()
#print("查找相似圖片")
for fromTitle in js['data']:
li.append(fromTitle['fromPageTitle'])
return "|".join(x for x in li)
def get_pic_content(slef, img_url):
box = (0, 41, 295, 186)
Image.open(img_url).crop(box).save(img_url)
def get_text(slef, img_url):
li = list()
ImageEnhance.Contrast(Image.open(img_url)).enhance(1.3).save(img_url)
imgs = Image.open(img_url)
imgs.filter(ImageFilter.BLUR).filter(ImageFilter.MaxFilter(23))
imgs.convert('L')
x_width, y_heigth = imgs.size
# 得到每一張圖片應(yīng)該的大小
width = x_width / 4
heigth = y_heigth / 2
for x_ in range(0, 2):
for y_ in range(0, 4):
left = y_ * width
right = (y_ + 1) * width
# 得到圖片位置
index = (x_ * 4) + y_
if x_ == 0:
box = (left, x_ * heigth + 21, right, (x_ + 1) * heigth + 21)
else:
box = (y_ * width, x_ * heigth + 21, (y_ + 1) * width, (x_ + 1) * heigth)
# 得到查詢地址
query_url = slef.__upload_pic__(imgs.crop(box), str(x_) + str(y_), slef.url)
# 進(jìn)行查詢订框,返回結(jié)果
text = slef.__get_query_content__(query_url)
# 計(jì)算坐標(biāo)
# 由于12306官方驗(yàn)證碼是驗(yàn)證正確驗(yàn)證碼的坐標(biāo)范圍,我們?nèi)∶總€(gè)驗(yàn)證碼中點(diǎn)的坐標(biāo)(大約值)
yanSol = ['35,35', '105,35', '175,35', '245,35', '35,105', '105,105', '175,105', '245,105']
# 將坐標(biāo)保存
li.append({yanSol[index]: text})
# print("識(shí)別結(jié)果:")
# print(text)
return li
if __name__ == "__main__":
img_url = "../images/code.png"
c = RecoginitionContainer("../images")
c.get_text(img_url)
閱讀代碼是小心我所使用的的切圖位置,是使用兩次循環(huán)得到的监氢。不明白的多看下就明白了布蔗。返回的坐標(biāo)點(diǎn)藤违,是進(jìn)行索引得到的浪腐。不明白的評(píng)論或仔細(xì)閱讀,都是無難度的代碼哦顿乒。
到了內(nèi)容比對(duì)咯
思路:
內(nèi)容比對(duì)三步走
第一步:整體比對(duì)
第二步:?jiǎn)蝹€(gè)字比對(duì)
第三步:去掉重復(fù)坐標(biāo)點(diǎn)(這步是放在第二步的)
第一步:從八張小圖片處可以獲取到圖片識(shí)別的結(jié)果议街,可能從百度文字識(shí)別api接口得到驗(yàn)證碼標(biāo)題。通過循環(huán)我們就可以比對(duì)了璧榄。
第二步:在小圖片或者驗(yàn)證碼標(biāo)題中我們可能識(shí)別的結(jié)果是不正確的特漩,那么我們第一步就算是半費(fèi)了吧雹,注意是半費(fèi)。那么我們就增大準(zhǔn)確性涂身,引入了第二步雄卷,單個(gè)字的進(jìn)行比對(duì)。這樣比對(duì)出來坐標(biāo)點(diǎn)就肯定很多了蛤售。注意重復(fù)丁鹉,第三步該上場(chǎng)了。
第三步:通過以上的比對(duì)悴能,我們可能得到較多的坐標(biāo)點(diǎn)揣钦,但是其中可能包含重復(fù),我們需要去掉漠酿。
def login_get_data(url, image_code, image_title):
# 刪除images所有文件
del_file("../images")
get_picture(url, image_code)
# 由于驗(yàn)證碼難度升級(jí)冯凹,成了兩個(gè)東西,比如:本子炒嘲,訂書機(jī)這種形式宇姚,那么
# 我需要進(jìn)行兩次分割,并進(jìn)行循環(huán)判斷才可以
point = list()
result = get_title_context(image_code, image_title)
if len(result) == 0:
print("識(shí)別標(biāo)題失敗,正在重新嘗試....")
login_get_data(url, image_code, image_title)
else:
print("標(biāo)題識(shí)別結(jié)果:")
print(result)
# 對(duì)圖片內(nèi)容進(jìn)行識(shí)別
print("開始對(duì)圖片內(nèi)容進(jìn)行識(shí)別....")
c = RecoginitionContainer("../images")
# 得到了坐標(biāo)和識(shí)別出來的內(nèi)容夫凸,或者相似圖片的標(biāo)題
lists = c.get_text(image_code)
# 進(jìn)行內(nèi)容比對(duì)
print("正在進(jìn)行內(nèi)容比對(duì)......")
for li in lists:
# 得到每一個(gè)坐標(biāo)點(diǎn)和內(nèi)容
# 循環(huán)標(biāo)題空凸,進(jìn)行比對(duì)
for title_text in result:
for po, value in li.items():
if title_text in value:
# 判斷當(dāng)前坐標(biāo)點(diǎn)是否存在
if po not in point:
print("識(shí)別出一個(gè)坐標(biāo)點(diǎn)")
point.append(po)
# 再次對(duì)標(biāo)題進(jìn)行分割
for tx in title_text:
for po, value in li.items():
if tx in value:
# 判斷當(dāng)前坐標(biāo)點(diǎn)是否存在
if po not in point:
print("識(shí)別出一個(gè)坐標(biāo)點(diǎn)")
point.append(po)
# 打印出圖片的內(nèi)容
print(point)
return point
該休息了
到了這個(gè)位置為止,所有的工作都基本是做完了的寸痢。
就差驗(yàn)證結(jié)果了呀洲。在12306官網(wǎng)找到驗(yàn)證碼校驗(yàn)接口,然后封裝數(shù)據(jù)進(jìn)行提交校驗(yàn)驗(yàn)證碼啼止。
接口:https://kyfw.12306.cn/passport/captcha/captcha-check
def check_captcha(point):
# 驗(yàn)證碼地址
check_url = "https://kyfw.12306.cn/passport/captcha/captcha-check"
data = {
"answer": ",".join(point),
"login_site": "E",
"rand": "sjrand"
}
# print(data)
response = req.post(check_url, data=data,
headers=headers, verify=False)
print(response.text)
if response.status_code != 200:
return False
code = response.json()['result_code']
# 取出驗(yàn)證結(jié)果道逗,4:成功 5:驗(yàn)證失敗 7:過期,8:我猜是不是同一個(gè)session,非法請(qǐng)求。
if str(code) == '4':
return True
else:
return False
注意:
在這里來了一定要注意献烦,我們需要是同一個(gè)session,不然將返回8滓窍,一定要注意啊
這樣就校驗(yàn)了,不成功繼續(xù)請(qǐng)求驗(yàn)證碼再次識(shí)別巩那,校驗(yàn)吏夯,直到返回成功4為止。
在來一劑猛藥--登錄
這個(gè)就是非常簡(jiǎn)單簡(jiǎn)單簡(jiǎn)單.....的事情了即横。封裝數(shù)據(jù)噪生,請(qǐng)求接口,處理返回?cái)?shù)據(jù)搞定的事情东囚,直接貼代碼跺嗽。
loginUrl = "https://kyfw.12306.cn/passport/web/login"
data = {
'username': 'your account',
'password': "yours password",
'appid': 'otn'
}
result = req.post(url=loginUrl, data=data, headers=headers, verify=False)
print("登錄返回結(jié)果:")
print(result.text)
看看自己的成果
從這張圖片上看到,我們登錄成功了的。
這次是運(yùn)氣好桨嫁,一次就識(shí)別成功了植兰。有的時(shí)候很多次都不會(huì)成功。識(shí)別效率低下璃吧。期待以后的deep learning技術(shù)更好楣导。
總結(jié)下自己
需要完善異常信息。
學(xué)無止境
我發(fā)布了文章之后畜挨,又要忍不住先給自己來一顆小紅心爷辙。你們來么?朦促?膝晾?