获取上交所和深交所所有股票的名称和交易信息,并保存到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()
北京理工大学嵩天老师的《Python网络爬虫与信息提取》