最好的掙錢方式是錢生錢,怎樣錢生錢呢霹期,錢生錢可以通過投資叶组,例如買股票、基金等方式历造,有人可能說買股票基金發(fā)財(cái)甩十,我沒這樣的命和運(yùn)氣。買股票基金靠的不只有命運(yùn)和運(yùn)氣吭产,更多靠的是長(zhǎng)期的經(jīng)驗(yàn)和對(duì)股票基金數(shù)據(jù)的分析侣监,今天我們使用scrapy框架來js逆向爬取某證信數(shù)據(jù)平臺(tái)的國內(nèi)指數(shù)成分股行情數(shù)據(jù)。
網(wǎng)頁分析
首先進(jìn)入某證信數(shù)據(jù)平臺(tái)國內(nèi)指數(shù)成分股行情數(shù)據(jù)并打開開發(fā)者模式臣淤,經(jīng)過簡(jiǎn)單查找發(fā)現(xiàn)國內(nèi)指數(shù)成分股行情的數(shù)據(jù)存放在如下圖的URL鏈接中:
這樣一看橄霉,很明顯,該網(wǎng)絡(luò)請(qǐng)求是POST請(qǐng)求邑蒋,URL鏈接姓蜂、請(qǐng)求表單沒什么加密,那么是不是獲取該URL鏈接的數(shù)據(jù)就很簡(jiǎn)單了呢医吊,這里我們簡(jiǎn)單的編寫代碼來請(qǐng)求該url鏈接的數(shù)據(jù)钱慢,具體代碼如下所示:
import requests
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36'
}
data={
'tdate':'2021-11-2',
'scode':'399001'
}
response=requests.post(url,headers=headers,data=data)
print(response.json())
按照上圖的內(nèi)容信息,我們這樣編寫爬蟲是沒有問題的卿堂,是可以獲取到數(shù)據(jù)的束莫,但運(yùn)行這段代碼就出了如下問題:
{'resultmsg': '未經(jīng)授權(quán)的訪問', 'resultcode': 401}
那么我們?cè)谡?qǐng)求頭中添加Cookie懒棉、Host、referer等參數(shù)览绿,運(yùn)行結(jié)果如下所示:
{'resultmsg': '無授權(quán)訪問策严,請(qǐng)聯(lián)系*********', 'resultcode': 401}
又出現(xiàn)了問題,這時(shí)挟裂,我們要觀察一下requests 請(qǐng)求頭中有哪些可疑的請(qǐng)求參數(shù)沒有添加到代碼中的headers中享钞,如下圖所示:
有兩個(gè)比較可疑的參數(shù)揍诽,首先我們添加第一個(gè)mcode參數(shù)到headers中诀蓉,運(yùn)行結(jié)果如下圖所示:
我們發(fā)現(xiàn)在headers中添加mcode參數(shù)就可以獲取到數(shù)據(jù),那么問題來了暑脆,mcode參數(shù)的值沒有規(guī)律可言渠啤,而且每次刷新網(wǎng)頁,mcode的值都會(huì)發(fā)現(xiàn)改變添吗,怎么辦好呢沥曹,這時(shí)我們可以通過js逆向來找出mcode值的生成方式。
js逆向加密
找出加密參數(shù)的生成方式大致可以分為四步:
尋找加密參數(shù)的方法位置找出來碟联;
設(shè)置斷點(diǎn)找加密方法妓美;
把加密方法寫入js文件;
調(diào)試js文件鲤孵。
尋找加密參數(shù)位置
打開開發(fā)者模式壶栋,點(diǎn)擊右上角三個(gè)小點(diǎn),選擇Search普监,搜索mcode贵试,如下圖所示:
搜索結(jié)果如下圖所示:
我們發(fā)現(xiàn)有三個(gè)js有mcode參數(shù)內(nèi)容,那該怎么辦呢凯正,這時(shí)我們可以精確一點(diǎn)搜索毙玻,在mcode后面就英文狀態(tài)的:,這時(shí)就只剩下第一個(gè)js了廊散,雙擊該js文件桑滩,如下圖所示:
在該js文件中,我們搜索mcode允睹,返回的結(jié)果有75個(gè)那么多运准,該怎么辦呢,這時(shí)我們發(fā)現(xiàn)在mcode上面一部分與我們要爬取的url有點(diǎn)關(guān)聯(lián)擂找,那么我們可以在該js文件中搜索url中最后的p_sysapi1015戳吝,如下圖所示:
這時(shí)我們發(fā)現(xiàn)搜索結(jié)果只有一個(gè)了,我們發(fā)現(xiàn)mcode是通過indexcode.getResCode()方法生成的贯涎,那么該方法有什么作用 呢听哭,我們還不知道,這時(shí)就需要通過設(shè)置斷點(diǎn)來找出加密方法函數(shù)。
設(shè)置斷點(diǎn)
我們?cè)谏厦娴膍code代碼行中設(shè)置斷點(diǎn)并刷新網(wǎng)頁陆盘,如下圖所示:
點(diǎn)擊上圖中的紅框釋放普筹,如下圖所示:
剛好出現(xiàn)了indexcode和getResCode,很明顯var time是和時(shí)間有關(guān)的隘马,而返回值 window.JSonToCSV.missjson()要調(diào)用time太防,但我們不知道.missjson()方法的作用是什么,這時(shí)我們搜索一下missjson酸员,如下圖所示:
搜索結(jié)果有兩個(gè)蜒车,其中一個(gè)是剛才調(diào)用的missjson()函數(shù),另外一個(gè)是missjson函數(shù)的具體代碼幔嗦∧鹄ⅲ看不懂這代碼的作用是什么,也沒關(guān)系邀泉,我們直接把這missjson()函數(shù)全部保存在一個(gè)js文件中嬉挡。
寫js文件
加密參數(shù)的方法已經(jīng)知道了,接下來我們將把加密參數(shù)missjson()函數(shù)寫入js文件中汇恤,這里我js文件名為mcode.js庞钢,如下圖所示:
這里需要注意的是:function必須要在missjson前面,這和JavaScript的語法有關(guān)因谎。
好了基括,js文件已經(jīng)寫好了,接下來我們調(diào)試一下js文件蓝角。
調(diào)試js文件
這里我們編寫程序來調(diào)試js文件阱穗,主要代碼如下圖所示:
import execjs
from os.path import realpath,dirname
import js2py
def get_js():
path = dirname(realpath(__file__)) + '/js/' + 'mcode' + '.js'
with open(path,'r',encoding='utf-8')as f:
read_js=f.read()
return_js=execjs.compile(read_js)
print(return_js)
if __name__ == '__main__':
get_js()
首先導(dǎo)入execjs、js2py這兩個(gè)調(diào)試js文件的庫使鹅,再自定義方法get_js()來讀取js文件揪阶,并調(diào)用execjs.compile()方法來執(zhí)行js程序。運(yùn)行結(jié)果如下圖所示:
發(fā)現(xiàn)沒有報(bào)錯(cuò)患朱,但沒有得到我們想要的參數(shù)值鲁僚,這是因?yàn)槲覀冞€沒有編寫time時(shí)間的參數(shù)進(jìn)去。主要代碼如下圖所示:
time1 = js2py.eval_js('Math.floor(new Date().getTime()/1000)')
mcode=return_js.call('missjson','{a}'.format(a=time1))
print(mcode)
return mcode
首先調(diào)用js2py.eval_js()方法來處理獲取的時(shí)間裁厅,在通過.call()方法將return_js加密數(shù)據(jù)和時(shí)間結(jié)合在一起冰沙,最后返回mcode。
運(yùn)行結(jié)果如下圖所示:
好了执虹,mcode參數(shù)成功獲取下來了拓挥,接下來將正式編寫代碼來爬取國內(nèi)指數(shù)成分股行情數(shù)據(jù)。
實(shí)戰(zhàn)演練
scrapy框架爬蟲
創(chuàng)建scrapy框架爬蟲很簡(jiǎn)單袋励,執(zhí)行如下代碼即可:
scrapy startproject <Scrapy項(xiàng)目名>
cd <Scrapy項(xiàng)目名>
scrapy genspider <爬蟲名字> <允許爬取的域名>
其中侥啤,我們的Scrapy項(xiàng)目名為Shares叉存,爬蟲名字為:shares荚坞,允許爬取的域名為:網(wǎng)站URL曼尊。
好了創(chuàng)建Scrapy項(xiàng)目后届囚,接下來我們創(chuàng)建一個(gè)名為js的文件夾來存放剛才編寫的js文件,并把調(diào)試js文件的Read_js.py文件放在Scrapy項(xiàng)目中赁炎,項(xiàng)目目錄如下圖所示:
這樣我們的爬蟲準(zhǔn)備工作就做好了醉箕,接下來正式編寫代碼來獲取數(shù)據(jù)。
itmes.py文件
在獲取數(shù)據(jù)前徙垫,我們先在items.py文件中讥裤,定義爬取數(shù)據(jù)的字段,具體代碼如下所示:
import scrapy
class SharesItem(scrapy.Item):
# define the fields for your item here like:
Transaction_date=scrapy.Field() #交易日期
Opening_price=scrapy.Field() #開盤價(jià)
Number_of_transactions=scrapy.Field()#成交數(shù)量
Closing_price=scrapy.Field() #收盤價(jià)
minimum_price=scrapy.Field() #最低價(jià)
Highest_price=scrapy.Field() #最高價(jià)
Securities_code=scrapy.Field() #證券代碼
Securities_abbreviation=scrapy.Field() #證券簡(jiǎn)稱
這里我們只定義了網(wǎng)頁展示給我們數(shù)據(jù)的字段松邪,要想獲取更多數(shù)據(jù)坞琴,可以根據(jù)下圖自行定義字段:
發(fā)送網(wǎng)絡(luò)請(qǐng)求
定義好字段后哨查,我們要在spiders爬蟲文件中的shares.py文件中編寫start_requests()方法來發(fā)送網(wǎng)絡(luò)請(qǐng)求逗抑,主要代碼如下所示:
def start_requests(self):
data1 = {
'tdate': '2021/10/11',
'scode': '399001'
}
url='網(wǎng)站URL/api/sysapi/p_sysapi1015'
yield scrapy.FormRequest(url,formdata=data1,callback=self.parse)
通過創(chuàng)建的data1字典來構(gòu)造Form Data表單數(shù)據(jù),由于是POST請(qǐng)求寒亥,所以我們要使用scrapy.FormRequest()方法來發(fā)送網(wǎng)絡(luò)請(qǐng)求邮府,發(fā)送網(wǎng)絡(luò)請(qǐng)求后,通過回調(diào)函數(shù)callback來將響應(yīng)內(nèi)容返回給parse()方法溉奕。
提取數(shù)據(jù)
在上一步中褂傀,我們成功獲取到了響應(yīng)內(nèi)容,接下來我們繼續(xù)編寫把響應(yīng)內(nèi)容解析并提取我們想要的數(shù)據(jù)加勤,主要代碼如下所示:
def parse(self, response):
p=response.json()
if p!=None:
pda=p.get('records')
for i in pda:
item=SharesItem()
item['Transaction_date']=i.get('交易日期')
item['Opening_price']=i.get('開盤價(jià)')
item['Number_of_transactions']=i.get('成交數(shù)量')
item['Closing_price']=i.get('收盤價(jià)')
item['minimum_price']=i.get('最低價(jià)')
item['Highest_price']=i.get('最高價(jià)')
item['Securities_code']=i.get('證券代碼')
item['Securities_abbreviation']=i.get('證券簡(jiǎn)稱')
yield item
我們把響應(yīng)內(nèi)容通過json()的格式來獲取下來仙辟,再通過.get()方法把我們想要的數(shù)據(jù)提取出來,最后通過yield生成器將數(shù)據(jù)返回給引擎鳄梅。
保存數(shù)據(jù)
在上一步中叠国,我們成功把數(shù)據(jù)提取出來并返回給引擎了,接下來在piplines.py文件中保存數(shù)據(jù)在MySQL數(shù)據(jù)庫中戴尸,主要代碼如下所示:
class mysqlPipeline:
conn = None
cursor = None
def open_spider(self,spider):
print('爬蟲開始K诤浮!孙蒙!')
self.conn=pymysql.Connection(host='localhost',user='root',passwd='123456',port=3306项棠,db='commtent1')
def process_item(self,item,spider):
self.cursor=self.conn.cursor()
sql2 = 'insert into data(Transaction_date,Opening_price,Number_of_transactions,Closing_price,minimum_price,Highest_price,Securities_code,Securities_abbreviation) value(%s,%s,%s,%s,%s,%s,%s,%s)'
print(list(item.values()))
self.cursor.execute(sql2,list(item.values()))
self.conn.commit()
def close_spider(self,spider):
print('爬蟲結(jié)束?媛汀O阕贰!')
self.cursor.close()
首先我們自定義pysqlPipeline類坦胶,然后編寫open_spider()方法來連接mysql數(shù)據(jù)庫透典,再通過process_item()方法來將數(shù)據(jù)存放在數(shù)據(jù)庫中歪玲,然后通過編寫close_spider()方法將數(shù)據(jù)庫關(guān)閉。
請(qǐng)求頭headers
接下來開始編寫請(qǐng)求頭headers掷匠,headers請(qǐng)求頭一般是在settings.py文件中編寫滥崩,首先在settings.py文件中找到DEFAULT_REQUEST_HEADERS代碼行并將注釋去掉,主要代碼如下圖所示:
from Shares.Read_js import get_js
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36'
mcode=get_js()
DEFAULT_REQUEST_HEADERS = {
'Referer': '網(wǎng)站url',
'Cookie':'Hm_lvt_489bd07e99fbfc5f12cbb4145adb0a9b=1635913057,1635925804,1635991345,1636252368; JSESSIONID=584FD4CCC7E980CAE09908DC0EF835FF; Hm_lpvt_489bd07e99fbfc5f12cbb4145adb0a9b=1636252373',
'mcode': mcode
}
LOG_LEVEL="WARNING"
ITEM_PIPELINES = {
# 'Shares.pipelines.SharesPipeline': 300,
'Shares.pipelines.mysqlPipeline': 301,
}
首先導(dǎo)入Shares.Read_js中的get_js方法讹语,并通過變量mcode來接收get_js()方法的返回值钙皮,最后通過LOG_LEVEL="WARNING"把運(yùn)行爬蟲程序的日志屏蔽,在setting.py文件中找到我們的ITEM_PIPELINES代碼行并將其注釋去掉顽决,開啟我們的項(xiàng)目管道短条。
執(zhí)行爬蟲
好了,所有代碼已經(jīng)編寫完畢了才菠,接下來將執(zhí)行如下代碼即可運(yùn)行爬蟲程序:
scrapy crawl shares
運(yùn)行結(jié)果如下圖所示:
這里我們只獲取到了一天的數(shù)據(jù)茸时,當(dāng)我們要獲取多天的數(shù)據(jù)怎么辦呢?
獲取多天數(shù)據(jù)
獲取多天數(shù)據(jù)很簡(jiǎn)單赋访,只需要調(diào)用pandas.period_range()方法即可可都,將發(fā)送網(wǎng)絡(luò)請(qǐng)求中的代碼修改為如下代碼即可:
datatime = pd.period_range('2021/10/11', '2021/10/12', freq='B')
for i in datatime:
data1 = {
'tdate': str(i),
'scode': '399001'
}
url='網(wǎng)站URL/api/sysapi/p_sysapi1015'
yield scrapy.FormRequest(url,formdata=data1,callback=self.parse)
其中freq='B'表示工作日,運(yùn)行結(jié)果如下圖所示:
好了蚓耽,爬取某證信股票行情就講到這里了渠牲,感謝觀看!2接啤签杈!