赞
踩
获取上交所和深交所所有股票的名称和交易信息,并保存到txt文件中。
根据前面的分析,以及目前掌握的技术,所以采用requests+bs4+re的技术路线进行实现。
Step1:从东方财富网获取股票列表——getHTMLText() getStockList(lst,stockURL)
Step2:根据股票列表逐个到百度股票获取个股信息——getStockInfo(lst,stockURL,fpath)
Step3:将结果存储到文件
写出程序的框架
import requests from bs4 import BeautifulSoup import traceback import re def getHTMLText(url): return "" def getStockList(lst, stockURL): return "" def getStockInfo(lst, stockURL, fpath): return "" def main(): pass main()
实现每一个函数的功能,使得程序能够运行得到我们想要的结果。
#!/usr/bin/env python #-*- coding:utf-8 -*- import requests from bs4 import BeautifulSoup import re import traceback # 追踪,便于调试 def getHTMLText(url): # 获取网页内容 try: r = requests.get(url) r.raise_for_status() r.encoding = r.apparent_encoding return r.text except: return "" def getStockList(lst,stockURL): # 获取股票列表 html = getHTMLText(stockURL) soup = BeautifulSoup(html,"html.parser") a = soup.find_all('a') # 需要的股票代码在a标签的“href"属性的值,故先获取全部a标签的列表 for i in a: try: href = i.attrs['href'] # q取“href"属性的值 lst.append(re.findall(r"s[hz]\d{6}",href)[0]) # 利用正则表达式提取股票代码 except: # 因为有的a标签没有股票代码,可以作为异常 continue def getStockInfo(lst,stockURL,fpath): # 获得每只个股的股票信息 for stock in lst: url = stockURL + stock + ".html" # 构建爬取股票信息的URL html = getHTMLText(url) # 获取股票信息网页内容 try: if html == "": # 有些网址打不开,或为空,则跳过 continue infoDict = {} # 以字典的形式保存股票信息,方便后面数据分析 soup = BeautifulSoup(html,"html.parser") stockInfo = soup.find('div',attrs = {'class':'stock-bets'}) # 获取股票信息所在的标签 name = stockInfo.find_all(attrs={'class':'bets-name'})[0] # 获取股票所在标签,这里注意find_all()返回的是一个bs4.element.ResultSet # 不能用name.string,原因是这里的name标签内有两个字符串子项,所以返回None,详情参照help文档 infoDict.update({'股票名称': name.text.split()[0]}) # 通过text以字符串形式获得name标签的所有字符串子项,然后用split()方法通过空格分隔字符串,提取股票名称 # 获取其它的键值对信息 keyList = stockInfo.find_all('dt') # 键在dt标签 valueList = stockInfo.find_all('dd') # 值在dd标签 for i in range(len(keyList)): key = keyList[i].text # 这里可以用keyList[i].string,获取键信息 val = valueList[i].text # 获取值信息 infoDict[key] = val # 往字典中添加键值对 with open(fpath, 'a', encoding='utf-8') as f:# 以追加模式打开,因为有中文所以用utf-8编码 f.write( str(infoDict) + '\n' ) # 这里为了输出美观,加了一个换行符 except: traceback.print_exc() # 追踪错误,便于调试 continue def main(): stock_list_url = "http://quote.eastmoney.com/stocklist.html" stock_info_url = "https://gupiao.baidu.com/stock/" output_file = "D://BaiduStockInfo.txt" slist = [] getStockList(slist,stock_list_url) getStockInfo(slist,stock_info_url,output_file) main()
程序报错:
分析错误代码,提示属性错误,None类型没有find_all属性,解决方案:定位到Alt+G定位到40行,做一个条件判断,排除None类型的情况。
更正后代码如下:
#!/usr/bin/env python #-*- coding:utf-8 -*- import requests from bs4 import BeautifulSoup import re import traceback # 追踪,便于调试 def getHTMLText(url): # 获取网页内容 try: r = requests.get(url) r.raise_for_status() r.encoding = r.apparent_encoding return r.text except: return "" def getStockList(lst,stockURL): # 获取股票列表 html = getHTMLText(stockURL) soup = BeautifulSoup(html,"html.parser") a = soup.find_all('a') # 需要的股票代码在a标签的“href"属性的值,故先获取全部a标签的列表 for i in a: try: href = i.attrs['href'] # q取“href"属性的值 lst.append(re.findall(r"s[hz]\d{6}",href)[0]) # 利用正则表达式提取股票代码 except: # 因为有的a标签没有股票代码,可以作为异常 continue def getStockInfo(lst,stockURL,fpath): # 获得每只个股的股票信息 for stock in lst: url = stockURL + stock + ".html" # 构建爬取股票信息的URL html = getHTMLText(url) # 获取股票信息网页内容 try: if html == "": # 有些网址打不开,或为空,则跳过 continue infoDict = {} # 以字典的形式保存股票信息,方便后面数据分析 soup = BeautifulSoup(html,"html.parser") stockInfo = soup.find('div',attrs = {'class':'stock-bets'}) # 获取股票信息所在的标签,有可能部分网页丢失,所以要做类型判断 if stockInfo: # stockInfo非None才执行 name = stockInfo.find_all(attrs={'class':'bets-name'})[0] # 获取股票所在标签,这里注意find_all()返回的是一个bs4.element.ResultSet # 不能用name.string,原因是这里的name标签内有两个字符串子项,所以返回None,详情参照help文档 infoDict.update({'股票名称': name.text.split()[0]}) # 通过text以字符串形式获得name标签的所有字符串子项,然后用split()方法通过空格分隔字符串,提取股票名称 # 获取其它的键值对信息 keyList = stockInfo.find_all('dt') # 键在dt标签 valueList = stockInfo.find_all('dd') # 值在dd标签 for i in range(len(keyList)): key = keyList[i].text # 这里可以用keyList[i].string,获取键信息 val = valueList[i].text # 获取值信息 infoDict[key] = val # 往字典中添加键值对 with open(fpath, 'a', encoding='utf-8') as f:# 以追加模式打开,因为有中文所以用utf-8编码 f.write( str(infoDict) + '\n' ) # 这里为了输出美观,加了一个换行符 except: traceback.print_exc() # 追踪错误,便于调试 continue def main(): stock_list_url = "http://quote.eastmoney.com/stocklist.html" stock_info_url = "https://gupiao.baidu.com/stock/" output_file = "D://BaiduStockInfo.txt" slist = [] getStockList(slist,stock_list_url) getStockInfo(slist,stock_info_url,output_file) main()
程序正常执行, 但是不显示进度,很慢,打开结果文件部分数据如下:
上面的代码初步实现了功能,但是很多代码还有待完善。依旧从性能和用户体验两个方面优化代码。以下是嵩天老师的优化。
从性能上:
#!/usr/bin/env python #-*- coding:utf-8 -*- import requests from bs4 import BeautifulSoup import re import traceback # 追踪,便于调试 def getHTMLText(url,code="utf-8"): # 获取网页内容 try: r = requests.get(url) r.raise_for_status() r.encoding = code return r.text except: return "" def getStockList(lst,stockURL): # 获取股票列表 html = getHTMLText(stockURL,"GB2312") soup = BeautifulSoup(html,"html.parser") a = soup.find_all('a') # 需要的股票代码在a标签的“href"属性的值,故先获取全部a标签的列表 for i in a: try: href = i.attrs['href'] # q取“href"属性的值 lst.append(re.findall(r"s[hz]\d{6}",href)[0]) # 利用正则表达式提取股票代码 except: # 因为有的a标签没有股票代码,可以作为异常 continue def getStockInfo(lst,stockURL,fpath): # 获得每只个股的股票信息 count = 0 for stock in lst: url = stockURL + stock + ".html" # 构建爬取股票信息的URL html = getHTMLText(url) # 获取股票信息网页内容 try: if html == "": # 有些网址打不开,或为空,则跳过 continue infoDict = {} # 以字典的形式保存股票信息,方便后面数据分析 soup = BeautifulSoup(html,"html.parser") stockInfo = soup.find('div',attrs = {'class':'stock-bets'}) # 获取股票信息所在的标签,有可能部分网页丢失,所以要做类型判断 if stockInfo: # stockInfo非None才执行 name = stockInfo.find_all(attrs={'class':'bets-name'})[0] # 获取股票所在标签,这里注意find_all()返回的是一个bs4.element.ResultSet # 不能用name.string,原因是这里的name标签内有两个字符串子项,所以返回None,详情参照help文档 infoDict.update({'股票名称': name.text.split()[0]}) # 通过text以字符串形式获得name标签的所有字符串子项,然后用split()方法通过空格分隔字符串,提取股票名称 # 获取其它的键值对信息 keyList = stockInfo.find_all('dt') # 键在dt标签 valueList = stockInfo.find_all('dd') # 值在dd标签 for i in range(len(keyList)): key = keyList[i].text # 这里可以用keyList[i].string,获取键信息 val = valueList[i].text # 获取值信息 infoDict[key] = val # 往字典中添加键值对 with open(fpath, 'a', encoding='utf-8') as f:# 以追加模式打开,因为有中文所以用utf-8编码 f.write( str(infoDict) + '\n' ) # 这里为了输出美观,加了一个换行符 count += 1 print("\r当前股票信息下载进度:{:.2f}%".format(count*100/len(lst)),end="") # \r实现光标不换行,覆盖之前的答应内容,end=""实现禁掉print函数的自动换行功能 except: count += 1 print("\r当前股票信息下载进度:{:.2f}%".format(count*100/len(lst)),end="") # \r实现光标不换行,覆盖之前的答应内容,end=""实现禁掉print函数的自动换行功能 traceback.print_exc() # 追踪错误,便于调试 continue def main(): stock_list_url = "http://quote.eastmoney.com/stocklist.html" stock_info_url = "https://gupiao.baidu.com/stock/" output_file = "D://BaiduStockInfo.txt" slist = [] getStockList(slist,stock_list_url) getStockInfo(slist,stock_info_url,output_file) main()
需要在cmd命令行下执行才能看到\r效果,在IDLE下\r的功能被禁止了。
执行过程如下:
性能上:
#!/usr/bin/env python #-*- coding:utf-8 -*- import requests from bs4 import BeautifulSoup import re import traceback # 追踪,便于调试 def getHTMLText(url,code="utf-8"): # 获取网页内容 try: r = requests.get(url) r.raise_for_status() r.encoding = code return r.text except: return "" def getStockList(lst,stockURL,fpath): # 获取股票列表 html = getHTMLText(stockURL,"GB2312") pat = r'(sh\d{6}|sz\d{6})' reg = re.compile(pat) sl = reg.findall(html) length = len(sl) count = 0 for i in sl: lst.append(i) # 这种方法不会改变原来传入的lst引用的地址 with open(fpath,'a',encoding='utf-8') as f: f.write(str(i) +'\n') count +=1 print('\r当前下载股票列表速度:{:.2f}%'.format(count*100/length),end='') def getStockInfo(lst,stockURL,fpath): # 获得每只个股的股票信息 count = 0 lstLength = len(lst) for stock in lst: url = stockURL + stock + ".html" # 构建爬取股票信息的URL html = getHTMLText(url) # 获取股票信息网页内容 try: if html == "": # 有些网址打不开,或为空,则跳过 continue infoDict = {} # 以字典的形式保存股票信息,方便后面数据分析 soup = BeautifulSoup(html,"html.parser") stockInfo = soup.find('div',attrs = {'class':'stock-bets'}) # 获取股票信息所在的标签,有可能部分网页丢失,所以要做类型判断 if stockInfo: # stockInfo非None才执行 name = stockInfo.find_all(attrs={'class':'bets-name'})[0] # 获取股票所在标签,这里注意find_all()返回的是一个bs4.element.ResultSet # 不能用name.string,原因是这里的name标签内有两个字符串子项,所以返回None,详情参照help文档 infoDict.update({'股票名称': name.text.split()[0]}) # 通过text以字符串形式获得name标签的所有字符串子项,然后用split()方法通过空格分隔字符串,提取股票名称 # 获取其它的键值对信息 keyList = stockInfo.find_all('dt') # 键在dt标签 valueList = stockInfo.find_all('dd') # 值在dd标签 keyListLength = len(keyList) for i in range(keyListLength): key = keyList[i].text # 这里可以用keyList[i].string,获取键信息 val = valueList[i].text # 获取值信息 infoDict[key] = val # 往字典中添加键值对 with open(fpath, 'a', encoding='utf-8') as f:# 以追加模式打开,因为有中文所以用utf-8编码 f.write( str(infoDict) + '\n' ) # 这里为了输出美观,加了一个换行符 count += 1 print("\r当前股票信息下载进度:{:.2f}%".format(count*100/lstLength),end="") # \r实现光标不换行,覆盖之前的答应内容,end=""实现禁掉print函数的自动换行功能 except: count += 1 print("\r当前股票信息下载进度:{:.2f}%".format(count*100/lstLength),end="") # \r实现光标不换行,覆盖之前的答应内容,end=""实现禁掉print函数的自动换行功能 traceback.print_exc() # 追踪错误,便于调试 continue def main(): stock_list_url = "http://quote.eastmoney.com/stocklist.html" stock_info_url = "https://gupiao.baidu.com/stock/" output_file1 = "D://BaiduStockList.txt" output_file2 = "D://BaiduStockInfo.txt" slist = [] getStockList(slist,stock_list_url,output_file1) print("\n") # 换行,避免两个进度条叠在一起 getStockInfo(slist,stock_info_url,output_file2) main()
运行过程图:
输出结果:
通过写这篇blog,很多语句都是一句一句在控制台下测试,理解每一步机器在做什么,虽然很耗时间,不过学到的东西比老师视频讲的要多,对于知识的理解也更深刻了。总结一下之前老师上课没有怎么提到的内容吧。
一般情况下,在python中函数的传参,很多人都在那儿争,到底是传的是它的值还是它的引用,其实最最最准确的说法,应该传的对象。那是什么意思呢,就是说,getStockList(lst,stockURL)函数第一个参数是list。那么这个类似的,假如说你是原地操作,比如说你list.append(),list.extend(),list.insert(),list.reverse(),list.sort()等,这些在原有的list的基础上进行操作的时候,他是改变原来那个list的内存地址的地方,但如果你写list等于另外一个新的list。这个时候list指到了新的内存地址。这样的话就导致了原来的内存地址的值没有变化,getStockInfo(lst,stockURL,fpath)调用时仍然为[]空列表。
重点看lst在getStockList(lst,stockURL)和getStockInfo(lst,stockURL,fpath)中参数传递的问题。
有兴趣的朋友可以看看试试下面这个代码,对比我最后优化的那个版本,有意思的是你会发现它很快就执行结束了,最后什么都没有输出。原因就在上面那段话。(作为初学者的我踩的又一个坑…)
#!/usr/bin/env python #-*- coding:utf-8 -*- import requests from bs4 import BeautifulSoup import re import traceback # 追踪,便于调试 def getHTMLText(url,code="utf-8"): # 获取网页内容 try: r = requests.get(url) r.raise_for_status() r.encoding = code return r.text except: return "" def getStockList(lst,stockURL): # 获取股票列表 html = getHTMLText(stockURL,"GB2312") pat = r'(sh\d{6}|sz\d{6})' reg = re.compile(pat) lst = reg.findall(html) # 这个相当于把lst这个标签重新贴到了一个新的地址上,导致getStockInfo()函数调用lst时为空列表 def getStockInfo(lst,stockURL,fpath): # 获得每只个股的股票信息 count = 0 lstLength = len(lst) for stock in lst: url = stockURL + stock + ".html" # 构建爬取股票信息的URL html = getHTMLText(url) # 获取股票信息网页内容 try: if html == "": # 有些网址打不开,或为空,则跳过 continue infoDict = {} # 以字典的形式保存股票信息,方便后面数据分析 soup = BeautifulSoup(html,"html.parser") stockInfo = soup.find('div',attrs = {'class':'stock-bets'}) # 获取股票信息所在的标签,有可能部分网页丢失,所以要做类型判断 if stockInfo: # stockInfo非None才执行 name = stockInfo.find_all(attrs={'class':'bets-name'})[0] # 获取股票所在标签,这里注意find_all()返回的是一个bs4.element.ResultSet # 不能用name.string,原因是这里的name标签内有两个字符串子项,所以返回None,详情参照help文档 infoDict.update({'股票名称': name.text.split()[0]}) # 通过text以字符串形式获得name标签的所有字符串子项,然后用split()方法通过空格分隔字符串,提取股票名称 # 获取其它的键值对信息 keyList = stockInfo.find_all('dt') # 键在dt标签 valueList = stockInfo.find_all('dd') # 值在dd标签 keyListLength = len(keyList) for i in range(keyListLength): key = keyList[i].text # 这里可以用keyList[i].string,获取键信息 val = valueList[i].text # 获取值信息 infoDict[key] = val # 往字典中添加键值对 with open(fpath, 'a', encoding='utf-8') as f:# 以追加模式打开,因为有中文所以用utf-8编码 f.write( str(infoDict) + '\n' ) # 这里为了输出美观,加了一个换行符 count += 1 print("\r当前股票信息下载进度:{:.2f}%".format(count*100/lstLength),end="") # \r实现光标不换行,覆盖之前的答应内容,end=""实现禁掉print函数的自动换行功能 except: count += 1 print("\r当前股票信息下载进度:{:.2f}%".format(count*100/lstLength),end="") # \r实现光标不换行,覆盖之前的答应内容,end=""实现禁掉print函数的自动换行功能 traceback.print_exc() # 追踪错误,便于调试 continue def main(): stock_list_url = "http://quote.eastmoney.com/stocklist.html" stock_info_url = "https://gupiao.baidu.com/stock/" output_file = "D://BaiduStockInfo.txt" slist = [] getStockList(slist,stock_list_url) getStockInfo(slist,stock_info_url,output_file) main()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。