在金融機(jī)構(gòu)工作,每天早晨的晨會不可避免,晨會主要的環(huán)節(jié)是了解當(dāng)天的上市公司公告沙庐。本文提供一個簡易實現(xiàn),自動抓取公告集錦佳吞,并郵件及時推送拱雏。
爬蟲主要功能結(jié)構(gòu)
notice_montage 可以做到拆箱即用。主要包括如下功能:
- 代碼實現(xiàn)
- 初始化配置文件及日志
- 使用requests抓取網(wǎng)頁頁面
- 解析入口頁底扳,解析公告頁內(nèi)容
- 分析公告頁內(nèi)容并進(jìn)行一些業(yè)務(wù)處理
- 郵件通知推送
- 代碼部署
- windows機(jī)器使用定時計劃自動執(zhí)行
- 阿里云服務(wù)自動執(zhí)行
代碼實現(xiàn)
1.1 初始化配置及日志
項目含有郵件地址等敏感信息铸抑,將其獨(dú)立出來形成配置文件,更利于項目部署花盐。對配置文件利用ConfigParser模塊進(jìn)行解析羡滑,代碼如下:
<pre>
def get_config_parser():
config_file_path = "notice_montage.ini"
cf = ConfigParser.ConfigParser()
cf.read(config_file_path)
return cf
解析配置
def init_config():
cf = get_config_parser()
global DEBUG, INTERVAL, WEBSITE
INTERVAL = int(cf.get("timeconf", "interval"))
DEBUG = cf.get("urlconf", "debug") == 'True'
WEBSITE = cf.get("urlconf", "website")
</pre>
配置文件notice_montage.ini,內(nèi)容如下:
<pre>
[urlconf]
website = http://www.ccstock.cn/meiribidu/jiaoyitishi/
debug = True
[timeconf]
interval = 10
[mailconf]
接收通知的郵箱,多個郵箱使用,分割
to_list = xxx@foxmail.com ,xxx@qq.com
設(shè)置服務(wù)器
mail_host = smtp.exmail.qq.com
替換為發(fā)件郵箱用戶名
mail_username = xxx
發(fā)件郵箱用戶名
mail_user = xxx
發(fā)件郵箱口令密碼
mail_pass = xxx
發(fā)件箱的后綴
mail_postfix = xxx
</pre>
爬蟲部署后算芯,自動運(yùn)行柒昏,適當(dāng)?shù)挠涗浺恍┪募罩荆欣谧粉櫯老x的運(yùn)作運(yùn)行狀況熙揍,做到有跡可查职祷。日志記錄利用logging模塊。
<pre>
日志記錄器
logger = logging.getLogger()
def init_log():
if DEBUG:
handler = logging.StreamHandler()
else:
handler = logging.FileHandler("notice_montage.log")
formatter = logging.Formatter(
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
</pre>
測試情況下届囚,日志輸出到控制臺有梆,會更方便調(diào)試;正式部署后意系,輸出到文件泥耀,要進(jìn)行切換,直接修改notice_montage.ini中的debug值蛔添。
1.2 使用requests抓取網(wǎng)頁頁面
<pre>
def download_get_html(url, charset="utf-8", timeout=10, num_retries=3):
UA = random.choice(user_agent_list)
headers = {
'User-Agent': UA,
'Content-Type': 'text/html; charset=' + charset
}
try:
response = requests.get(url, headers=headers,
timeout=timeout)
response.encoding = charset
if response.status_code == 404:
logger.debug('get 404: %s ', url)
return None
else:
logger.debug('get : %s ', url)
return response.text
except:
if num_retries > 0:
time.sleep(10)
logger.debug('正在嘗試痰催,10S后將重新獲取倒數(shù)第 %d 次', num_retries)
return download_get_html(url, charset, timeout, num_retries - 1)
else:
logger.debug('嘗試也不好使了!取消訪問')
return None
</pre>
download_get_html 是一個和業(yè)務(wù)無關(guān)的工具函數(shù)迎瞧,主要使用request的get方法下載指定URL的內(nèi)容夸溶。為了讓下載更友好,模擬了User-Agent凶硅,并自動嘗試3次缝裁。
1.3 使用BeautifulSoup解析頁面內(nèi)容
使用download_get_html獲取頁面內(nèi)容后,使用BeautifulSoup對其進(jìn)行解析足绅,獲取需要的信息捷绑。
解析入口列表頁內(nèi)容
入口頁韩脑,是所有交易公告的列表清單,使用chrome的開發(fā)者工具胎食,查看頁面代碼如下圖:
列表按照時間進(jìn)行倒序排列扰才,晨報只需要最新的一條公告集錦頁面,所以只需要找到class為listMain的div的第一個li類型子元素厕怜,代碼如下:
<pre>
獲取當(dāng)期集錦的url
def parser_list_page(html_doc, now):
soup = BeautifulSoup(html_doc, 'lxml', from_encoding='utf-8')
# 只找第一個標(biāo)簽
tag = soup.find("div", class_="listMain").find("li")
link_tag = tag.find("a")
span_tag = tag.find("span")
page_url = link_tag['href']
# 截取日期
date_string = span_tag.string[0:10]
if date_string == now:
return page_url
else:
return None
</pre>
now參數(shù)為當(dāng)期的時間衩匣,用于核對當(dāng)期公告是否更新。已更新則返回次日集錦鏈接粥航,未更新則返回空琅捏。
解析內(nèi)容頁內(nèi)容
獲取公告集錦頁地址后,繼續(xù)使用download_get_html獲取內(nèi)容頁內(nèi)容,如下圖:
只需要找到id為newscontent的div即可递雀,代碼如下:
<pre>
def parser_item_page(html_doc, now):
soup = BeautifulSoup(html_doc, 'lxml', from_encoding='utf-8')
# title = soup.find("h1").string
newscontent = soup.find("div", id="newscontent")
html = newscontent.prettify()
return html
</pre>
1.4 分析頁面內(nèi)容
公告中數(shù)字都采用統(tǒng)計技術(shù)柄延,并不利用閱讀,我們利用正則將其進(jìn)行處理缀程。
例如:
<pre>
杰克股份(603337)7月23日晚間公告搜吧,截至2017年7月21日,公司2017年員工持股計劃通過二級市場買入方式增持公司股票3杨凑,468滤奈,534股,占公司已發(fā)行股本1.68%撩满。成交合計金額133蜒程,786,450.45元伺帘,成交均價38.57元昭躺。
</pre>
將其中的股數(shù)和成交金額進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換后如下:
<pre>
杰克股份(603337)7月23日晚間公告伪嫁,截至2017年7月21日领炫,公司2017年員工持股計劃通過二級市場買入方式增持公司股票3,468张咳,534股(346.8534萬股)驹吮,占公司已發(fā)行股本1.68%。成交合計金額133晶伦,786,450.45元(1.3378645045億元)啄枕,成交均價38.57元婚陪。
</pre>
這樣閱讀起來更符合習(xí)慣。格式化股份數(shù)代碼如下:
<pre>
格式化股份計數(shù)
def transform_gu(lineText):
p = re.compile(u"[\d频祝,]\d+股")
searchObj = re.findall(p, lineText)
if searchObj:
for x in xrange(0, len(searchObj)):
s1 = searchObj[x]
ns = filter(lambda ch: ch in '0123456789', s1)
nb = float(ns)
if nb >= 100000000:
s2 = str(nb / 100000000) + "億股"
lineText = lineText.replace(s1, s1 + "(" + s2 + ")")
elif nb >= 10000:
s2 = str(nb / 10000) + "萬股"
lineText = lineText.replace(s1, s1 + "(" + s2 + ")")
return lineText
</pre>
核心正則代碼<code>u"[\d泌参,]\d+股"</code>,表示至少含有任意個數(shù)字+,脆淹,然后連接至少一個數(shù)字,以“股”字結(jié)尾沽一,匹配上3盖溺,468,534股這樣的字符
串铣缠。
還可以對公告集錦匹配自選股池烘嘱,做重點(diǎn)提醒;對大股東增持的利好消息蝗蛙,進(jìn)行特殊提示蝇庭。篇幅有限,涉及具體業(yè)務(wù)捡硅,處理方法也比較一致哮内,本文就不進(jìn)行展示。
1.5 郵件通知推送
公告獲取后壮韭,立即推送給目標(biāo)用戶北发,做到快人一步。
<pre>
def send_notice_mail(html, now):
cf = get_config_parser()
to_list = cf.get("mailconf", "to_list").split(",")
mail_host = cf.get("mailconf", "mail_host")
mail_username = cf.get("mailconf", "mail_username")
mail_user = cf.get("mailconf", "mail_user")
mail_pass = cf.get("mailconf", "mail_pass")
mail_postfix = cf.get("mailconf", "mail_postfix")
me = "AStockMarketNoticeWatcher" + "<" +
mail_username + "@" + mail_postfix + ">"
msg = MIMEMultipart()
subject = now + ' 日 - 二級市場重要公告集錦'
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = me
msg['To'] = ";".join(to_list)
mail_msg = html
# 郵件正文內(nèi)容
msg.attach(MIMEText(mail_msg, 'html', 'utf-8'))
try:
server = smtplib.SMTP()
server.connect(mail_host)
server.ehlo()
server.starttls()
server.login(mail_user, mail_pass)
server.sendmail(me, to_list, msg.as_string())
server.close()
logger.debug('sent mail successfully')
except smtplib.SMTPException, e:
logger.debug('Error: 無法發(fā)送郵件 %s ', repr(e))
</pre>
代碼部署
2.1 windows 系統(tǒng)下定時任務(wù)自動執(zhí)行
windows的定時任務(wù)喷屋,需要先編寫bat腳本琳拨,代碼非常簡單,類似<code>python notice_montage.py</code>逼蒙,autorun.bat 代碼如下:
<pre>
:: 自動運(yùn)行腳本
:: 需更換為本機(jī)路徑
c:\python27\python.exe D:\xampp\htdocs\ding\morning\notice_montage.py %*
</pre>
在自動執(zhí)行前从绘,記得切換notice_montage.ini的debug值為False,留下日志文件是牢,方便跟蹤結(jié)果僵井。
打開【計劃任務(wù)程序】,選擇右側(cè)【操作】中的【創(chuàng)建任務(wù)】
需要注意的是驳棱,勾選【不管用戶是否登錄都要運(yùn)行】批什,這樣系統(tǒng)待機(jī)狀態(tài)下也可以執(zhí)行∩缃粒【觸發(fā)器】頁簽中驻债,新建觸發(fā)器:
根據(jù)需要,設(shè)置成每天定點(diǎn)到10點(diǎn)即可形葬。(實際代碼中合呐,進(jìn)行了自動嘗試,如果公告10點(diǎn)沒更新笙以,會10分鐘后嘗試一次淌实,自動嘗試3次)
【操作】頁簽中,新建操作:
程序和腳本,選擇前面創(chuàng)建的autorun.bat
確定后拆祈,就可以自動運(yùn)行了恨闪,每天收到公告。
2.2 阿里云服務(wù)器自動執(zhí)行
服務(wù)器部署好python/virtualenv后放坏,編寫crontab命令
<pre>
[root@iZ253amoxhcZ morning]# crontab -l
00 22 * * 0-5 /uy/flask-venv/bin/python notice_montage.py
</pre>
其中<code>/uy/flask-venv/bin/python</code>是virtualenv的路徑咙咽,<code>00 22 * * 0-5</code> 表示周日-周五每天22點(diǎn)調(diào)度。
最后淤年,爬蟲源代碼在(叮)[https://github.com/thebe2/ding] 钧敞,喜歡的請給個星。