宣告:以下內容均為我個人的理解,如果發現錯誤或者疑問可以聯絡我共同探討
爬蟲介紹
爬蟲是一種按照一定規則自動抓取網路上的資訊資料的程式。我們身處一個大資料的時代,可以通過爬蟲獲取到我們所需要的資料(遵從robots協議中的規則)。
網站介紹
叩富網是江西博辰網路科技公司旗下的一個專業網站。模擬炒股網站初建於2000年,2006年被博辰網路收購,並開始規範系統的運作。目前使用者100多萬,日均IP數10萬左右。是國內唯一一家專業致力於模擬炒股開發和運營的網站。公司旗下有有獎大賽站、免費大賽站以及和其他證券公司合作的網站。
編寫爬蟲的原因和用途
本人閒暇時間會學習投資理財相關內容,並通過叩富網進行模擬炒股,本次想通過編寫爬蟲來程式化自己的模擬交易
robots協議
什麼是robots協議
robots協議以robots.txt檔案形式呈現,是網站中給各類爬蟲規定爬取範圍的檔案,robots.txt存放在網站的根目錄下。我們準備爬取某個網站時,首先應該檢視我們需要的資料該網站是否允許我們爬取,當我們爬取規則之外的資料時,該網站有權利起訴我們非法獲取資料。
robots協議的構成
由User-agent、Allow、Disallow構成。User-agent後面的內容是具體的爬蟲名,如百度爬蟲為Baiduspider,則代表所有爬蟲。Allow後面的內容是允許爬取的URL路徑,如/.jpg$表示可以爬取該網站下的所有.jpg圖片,/表示所有路徑均允許爬取。Disallow後面的內容是不允許爬取的URL路徑,同Allow相反。
常見的規則有哪些
允許爬蟲獲取所有內容:
User-agent: *
Disallow:
# 或者
User-agent: *
Allow: /
禁止爬取所有內容:
User-agent: *
Disallow: /
禁止訪問網站中所有動態頁面
User-agent: *
Disallow: /*?*
禁止搜尋引擎抓取網站上所有圖片
User-agent: *
Disallow: /*.jpg$
Disallow: /*.jpeg$
Disallow: /*.gif$
Disallow: /*.png$
Disallow: /*.bmp$
Feeling
爬蟲可以使我們更加高效的獲取到網際網路中的各類資料,但網路不是法外之地我們也要在合規合法的基礎上進行爬取,尊重每一個網站開發者,敬畏法律。
requests
requests庫是學習爬蟲入門最適合的一個第三方庫,它是將Python內建的urllib進行深度封裝的庫。是一個非常成熟的HTTP客戶端庫,當然他也並非完美,我們後期也可以在其基礎上進行補充形成適合自己的一個庫。
快速上手
中文文件:docs.python-requests.org/zh_CN/lat...
官方示例:docs.python-requests.org/zh_CN/lat...
獲取robots協議,檢視可爬取範圍
使用get請求獲取協議內容,並將其輸出控制檯檢視發現規則允許我們爬取整站內容
登陸叩富網
在Chrome瀏覽器中通過開發者選項中的Network選項卡中發現,登陸的時候有一個login.html
我們可以在裡面看到請求URL、請求方式和Form Data所需要的內容,根據這個編寫一個請求檢視一下獲取到的資料
由於是字串格式,所以出現了我們看不懂的字元,通過json轉成Python中的字典格式再檢視
可以看到顯示登陸成功了,接下來通過獲取到的cookie就可以獲取到我們需要的各種資訊了
接下來我們將程式碼優化一下變成一個方法,讓登陸後的cookie可以在以後任意需要的地方使用
import requests
import json
from lxml import etree
def login(username, password):
"""
登陸叩富網
:param username: 使用者名稱
:param password: 密碼
:return: 登陸成功返回cookie,失敗丟擲對應異常
"""
login_url = 'http://www.cofool.com/Passport/login.html'
data = {
'username': username,
'password': password
}
try:
response = requests.post(url=login_url, data=data)
except Exception as e:
raise Exception("登陸失敗,原因為:{}".format(e))
content = json.loads(response.content.decode())
if content['status'] == 0:
cookie = response.cookies.get_dict()
return cookie
else:
raise Exception("登陸失敗,原因為:{}".format(content['info']))
在出現登入失敗的時候丟擲異常,並顯示錯誤資訊
Feeling
爬蟲是我們與開發者的博弈,我們需要站在他們的角度去分析他們開發的過程,從而更好的理解並去設計我們的相關爬蟲程式。通常爬蟲程式都不是一次就完成的,需要通過我們不斷根據請求的反饋去修改程式,最終通過多層解析得到結果。
獲取賬戶資訊
練習區資訊
通過對相關請求的分析,練習區資訊在http://www.cofool.com/Trade/Stock/index/gid/2.html
這個URL下
對頁面分析我們所需要的資料在class為top_ts的div下
XPath
XPath是一門在XML文件中查詢資訊的語言,可以幫助我們在爬蟲中對獲取資料進行查詢得到我們需要的內容。
推薦從大佬崔慶才的個人部落格學習XPath及爬蟲的相關內容
開始通過XPath獲取對應資料,考慮到多個資料在相同的font標籤下,可以一次性獲取所有標籤然後進行處理
def get_account_info(query_category):
"""
根據query_category獲取賬戶相關資訊
:param query_category: 查詢類別資訊,賬戶資訊:ai 持倉狀態:ap 當日委託:ac 當日成交:ad 歷史成交:hd 股票收益明細:sd 日資產增長明細:da
月資產增長明細:ma 榮耀榜 ho
:return:
"""
cookie = login('使用者名稱', '密碼')
if query_category not in ['ai', 'ap', 'ac', 'ad', 'hd', 'sd', 'da', 'ma', 'ho']:
raise Exception('查詢類別不存在')
if query_category == 'ai':
url = 'http://www.cofool.com/Trade/Stock/index/gid/2.html'
# 抓取賬戶相關資料
try:
response = requests.get(url=url, cookies=cookie).content.decode()
except Exception as e:
raise Exception("獲取賬戶資料失敗,原因為:{}".format(e))
if '總盈利率' not in response:
raise Exception("獲取賬戶資料失敗,未獲取到正確資訊")
# 使用xpath對抓取到的資料進行清洗得到我們需要的資料
html = etree.HTML(response)
account_info = html.xpath('//div [@class="top_ts"]/div//font/text()')
if len(account_info) != 4:
raise Exception("獲取賬戶資料失敗,未獲取足夠的賬戶資料")
gross_profit_rate = account_info[0] # 總收益率
initial_funding = account_info[1] # 初始資金
number_of_participants = account_info[2] # 參賽人數
average_income = account_info[3] # 平均收益率
overall_ranking = html.xpath('//div [@class="top_ts"]/div//p[2]/b/text()')[0] # 總排名
try:
total_assets = float(''.join(re.search('\d,\d*,\d*.\d*', html.xpath(
'//*[@id="left2"]/table/tbody/tr[1]/td[@class="btom zjsr"]/text()')[0])[0].split(',')))
available_funds = float(''.join(re.search('\d,\d*,\d*.\d*', html.xpath(
'//*[@id="left2"]/table/tbody/tr[1]/td[@class="btom zjl"]/text()')[0])[0].split(',')))
except Exception as e:
raise Exception("獲取賬戶資料失敗,{}".format(e))
return {'總收益率': gross_profit_rate, '初始資金': initial_funding, '參賽人數': number_of_participants,
'平均收益率': average_income, '總排名': overall_ranking, '總資產': total_assets, '可用資金': available_funds}
持倉、交易、業績資訊
通過對相關請求的分析,當前持倉、當日委託、當日成交、歷史成交與業績報告都用同一個URL進行請求,只是請求中FormData部分資料不同,分析各頁面資料發現當前持倉、當日委託、當日成交、歷史成交資料類似,將其作為同一個型別進行爬取做簡單修改即可。分頁相關資訊需要根據頁碼資料進行追加處理
...
url = 'http://www.cofool.com/Trade/Stock/tradeItem.html'
data = {'gid': 'gid', 'uid': 'uid', 'web_id': 'web_id}
# 根據查詢條件設定對應的type值
if query_category == 'ap':
data['type'] = 'position'
elif query_category == 'ac':
data['type'] = 'entrust'
elif query_category == 'ad':
data['type'] = 'turnover'
elif query_category == 'hd':
data['type'] = 'history'
elif query_category == 'sd':
data['type'] = 'earnings'
elif query_category == 'da':
data['type'] = 'dayasset'
elif query_category == 'ma':
data['type'] = 'monthasset'
elif query_category == 'ho':
data['type'] = 'honor'
# 第一次獲取對應資訊,當資訊不存在時返回'暫無交易的資料!'
try:
response = requests.post(url=url, data=data).content.decode()
except Exception as e:
raise Exception('獲取賬戶資料失敗,原因為:{}'.format(e))
if ' 暫無交易的資料!' in response:
return '暫無交易的資料!'
html = etree.HTML(response)
# 當有資訊時獲取頁碼數值,
number_of_pages = len(html.xpath('//div [@class = "clearfix fr"]/a/text()')) + 1
# 獲取對應表頭名
col_name = html.xpath('//tr/th/text()')
# 設定臨時儲存字典
temp_dict = {}
# 根據頁碼資訊進行第二次訪問獲取資訊
for i in range(1, number_of_pages + 1):
# 新增頁碼資料
data['p'] = i
try:
response = requests.post(url=url, data=data).content.decode()
except Exception as e:
raise Exception('獲取賬戶資料失敗,原因為:{}'.format(e))
# 由於爬取的資料存在大量\n和空格,先進行一次簡單的清洗
response = re.sub('\n\s|\s', '', response)
html = etree.HTML(response)
# 分別獲取各列資訊
for j in range(len(col_name)):
xpath_index = j + 1
xpath_values = html.xpath('//tr/td[{}]//text()|//tr/td[{}]/font/text()|//tr/td[{}]/text()'.format(xpath_index, xpath_index,xpath_index))
if i == 1:
temp_dict[col_name[j]] = xpath_values
# 當出現第二頁及其以上時,進行資料追加處理
else:
temp_dict[col_name[j]].extend(xpath_values)
return pd.DataFrame(temp_dict)
Feeling
爬蟲中資料清洗的過程通常也非常熬人,網站開發者的水平高低與反爬的難度,會導致許多資料清洗起來非常複雜,可能會花費大量的時間還得不到你想要的結果,這時候我們需要保持一顆平常心,針對出現的問題一個一個去解決,只要堅持下去一定能解決的!
賬戶操作
股票資訊
接下來編寫賬戶操作方面的爬蟲,要交易首先要拿到進行交易股票的相關資訊,通過分析相關請求後發現其URL為http://www.cofool.com/Trade/Stock/stockQuote.html
,這個比較簡單很快就可以完成對應程式碼
def get_stock_info(stock_code):
"""
根據股票程式碼獲取相關價格
:param code:股票程式碼
:return: 相關價格
"""
url = "http://www.cofool.com/Trade/Stock/stockQuote.html"
data = {
"code": stock_code,
"uid": 'uid',
}
cookie = login('賬戶名', '密碼')
try:
response = json.loads(requests.post(url=url, data=data, cookies=cookie).content.decode("utf-8-sig"))
except Exception as e:
raise Exception('獲取賬戶資料失敗,原因為:{}'.format(e))
stock_info = response["info"]
info_dict = {
"high_limit": stock_info["surgedLimit"], # 漲停價
"low_limit": stock_info["declineLimit"], # 跌停價
"stock_name": stock_info['stockName'], # 股票名稱
'currentPrice': stock_info["currentPrice"], # 當前價
}
return info_dict
股票交易
通過觀察發現買入與賣出操作的URL差距非常小,並且請求引數也高度相似,可以將其放在同一個方法中
def tradeing(stock_code, tradeing_type, amount=0):
"""
股票交易,並返回交易狀態
:param stock_code:股票程式碼
:param tradeing_type:交易型別
:param amount:交易數量,預設為0防止出現沒有填寫的情況
:return:交易狀態
"""
cookie = login('賬戶名', '密碼')
# 獲取股票相關資訊
info_dict = get_stock_info(stock_code)
price = info_dict["high_limit"]
# 配置相關資料
data = {
"stockName": info_dict['stock_name'],
"code": stock_code,
"uid": 'uid',
"gid": 2,
"orderPrice": price,
"orderAmount": amount,
"declineLimit": info_dict['low_limit'],
"surgedLimit": info_dict['high_limit']
}
if tradeing_type == 'buy':
url = "http://www.cofool.com/Trade/Stock/buy.html"
# 買入操作,檢測買入數量防止超過最大買入數量
available_funds = get_account_info('ai')['可用資金']
if amount < available_funds / float(price):
data["orderAmount"] = amount
else:
raise Exception('購買數量超過最大可購買數量')
elif tradeing_type == 'sell':
url = "http://www.cofool.com/Trade/Stock/sell.html"
# 賣出操作,從持倉資訊中獲取可賣出數量
data["orderAmount"] = get_amount(stock_code)
try:
content = \
requests.post(url=url, data=data, cookies=cookie).content.decode("utf-8-sig")
status = json.loads(content, encoding="unicode_escape")["status"]
except Exception as e:
raise Exception('獲取賬戶資料失敗,原因為:{}'.format(e))
if status == 1:
print("買入股票:{},股數:{}".format(stock_code, amount))
return 0
else:
raise Exception("買入失敗,失敗原因為:{}".format(json.loads(content, encoding="unicode_escape")["info"]))
持倉數量
在賣出交易的時候需要獲取賬戶中持倉的數量
def get_amount(code):
"""根據股票程式碼獲取可交易數量
:param code:股票程式碼
:return:對應股票可用數量
"""
url = "http://www.cofool.com/Trade/Stock/sellAmount"
data = {
"code": code,
"uid": 'uid',
"web_id": 'web_id'
}
cookie = login('賬戶名', '密碼')
try:
content = json.loads(requests.post(url=url, data=data, cookies=cookie).content.decode('unicode_escape'))
except Exception as e:
raise Exception("獲取股數資料失敗,{}".format(e))
status = content['status']
if status == 1:
amount = content['info']['frozen_amount']
return amount
else:
raise Exception(content['info'])
到這裡叩富網的爬蟲基本完成了,最後將轉化為類就可以了,完整程式碼可以在我的GitHub倉庫檢視最新的程式碼
本作品採用《CC 協議》,轉載必須註明作者和本文連結