1. 行业板块
行业板块与概念股在定义上还是有很大区别的。
一般来说,概念板块的风险更大,基于某个消息被短期炒作,很不稳定,因此风险更大
行业板块是按股票行业进行分类,往往注重于长期,稳定性更高。
在实际投资上,短期可以根据按「 市场热点 」从概念股进行选股投资,中长期还是建议按「 行业板块 」选股进行投资。
2. 爬取相关板块及个股列表
目标对象:
aHR0cDovL3N1bW1hcnkuanJqLmNvbS5jbi9oeWJrLw==
2-1 板块列表
首先,我们使用 「 Toggle JavaScript 」插件发现页面中的行业板块数据来源于下面的请求结果
http://**/?q=cn|bk|17&n=hqa&c=l&o=pl,d&p=1020&_dc=1650680429759
其中,参数为 p 和 _dc 为可变参数,p 代表页码数(从 1 开始),_dc 代表 13 位的时间戳,其他查询参数都是固定内容
然后,我们编写代码获取响应数据,使用正则表达式匹配出行业列表的数据
...
self.ps_url = 'http://**/?q=cn|bk|17&n=hqa&c=l&o=pl,d&p={}050&_dc={}'
....
def __get_timestramp(self):
"""
获取13位的时间戳
:return:
"""
return int(round(time.time() * 1000))
...
def get_plates_list(self, plate_keyword):
"""
获取所有板块
:return:
"""
plates = []
index = 0
while True:
url = self.ps_url.format(index + 1, self.__get_timestramp())
# 解析数据
resp = self.session.get(url, headers=self.headers).text
match = re.compile(r'HqData:(.*?)};', re.S)
result = json.loads(re.findall(match, resp)[0].strip().replace("\n", ""))
if not result:
break
# 根据关键字,过滤有效板块
temp_plate_list = [item for item in result if plate_keyword in item[2]]
index += 1
for item in temp_plate_list:
print(item)
plates.append({
"name": item[2],
"plate_path": item[1],
"up_or_down": str(item[10]) + "%",
"top_stock": item[-6]
})
return plates
...
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
最后,根据关键字对板块进行一次筛选,通过板块名、板块路径 PATH、板块涨跌幅、最大贡献股票名称重新组装成一个列表
注意:通过分析页面发现,根据板块路径 PATH 可以组装成行业板块个股列表页面 URL
比如,行业板块 PATH 为 400128925,那么行业板块对应个股列表的页面 URL 为
http://summary.**/hybk/400128925.shtml
2-2 行业个股列表
爬取行业个股列表和上一步数据展示逻辑一样,个股列表数据同样来源于下面请求的结果
http://**/?q=cn|s|bk{}&c=m&n=hqa&o=pl,d&p={}020&_dc={}
其中,bk 后面对应行业板块 PATH,p 代表页码数,_dc 代表 13 位的时间戳
...
# 个股
self.stock_url = 'http://**/?q=cn|s|bk{}&c=m&n=hqa&o=pl,d&p={}020&_dc={}'
....
def get_stock_list(self, plate_path):
"""
获取某一个板块下所有的个股信息
包含:股票名称、最新价格、涨跌幅、市盈率
:param plate_info:
:return:
"""
index = 0
stocks = []
while True:
url = self.stock_url.format(plate_path, index + 1, self.__get_timestramp())
resp = self.session.get(url, headers=self.headers).text
match = re.compile(r'HqData:(.*?)};', re.S)
result = json.loads(re.findall(match, resp)[0].strip().replace("\n", ""))
if not result:
break
index += 1
for item in result:
if item[-1] < 0:
continue
stocks.append({
"stock_name": item[2],
"pe": item[-1],
"price": item[8],
"up_or_down": str(item[12]) + "%"
})
# 按pe降序排列
stocks.sort(key=lambda x: x["pe"])
return stocks
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
通过正则表达式对响应结果进行匹配后,获取个股的名称、PE 市盈率、价格、涨跌幅 4 个关键数据
最后,对个股列表按 PE 进行升序排列后直接返回
3. 服务化
当然,我们可以将这部分逻辑服务化供前端使用,以此提升用户的体验性
比如,使用 FastAPI 可以快速创建两个服务:根据关键字获取行业板块列表、根据板块路径获取个股列表
from pydantic import BaseModel
# 板块
class Plate(BaseModel):
content: str # 关键字
# 板块下的个股
class PlateStock(BaseModel):
plate_path: str # 板块路径
#===========================================================
...
# 获取板块列表
.post("/xag/plate_list")
async def get_plate_list(plate: Plate):
pstock = PStock()
try:
result = pstock.get_plates_list(plate.content)
return success(data=result, message="查询成功!")
except Exception as e:
return fail()
finally:
pstock.teardown()
# 获取某一个板块下的所有股票列表
.post("/xag/plate_stock_list")
async def get_plate_list(plateStock: PlateStock):
pstock = PStock()
try:
result = pstock.get_stock_list(plateStock.plate_path)
return success(data=result, message="查询成功!")
except Exception as e:
return fail()
finally:
pstock.teardown()
...
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
前端以 Uniapp 为例,使用 uni-table 组件展示行业板块列表及个股列表
部分代码如下:
//个股列表 platestock.vue
...
<view class="box">
<uni-forms ref="baseForm" :modelValue="baseFormData" :rules="rules">
<uni-forms-item label="关键字" required name="content">
<uni-easyinput v-model="baseFormData.content" placeholder="板块关键字" />
</uni-forms-item>
</uni-forms>
<button type="primary" @click="submit('baseForm')">提交</button>
<!-- 结果区域 -->
<view class="result" v-show="result.length>0">
<uni-table ref="table" border stripe emptyText="暂无数据">
<uni-tr class="uni-item">
<uni-th align="center" class="uni-th" width="100%">板块</uni-th>
<uni-th align="center" class="uni-th" width="100%">涨跌幅</uni-th>
<uni-th align="center" class="uni-th" width="100%">强势股</uni-th>
</uni-tr>
<uni-tr class="uni-item" v-for="(item, index) in result" :key="index" @row-click="rowclick(item)">
<uni-td class="uni-th" align="center">{{ item.name }}</uni-td>
<uni-td align="center" class="uni-th">{{ item.up_or_down }}</uni-td>
<uni-td align="center" class="uni-th">{{ item.top_stock }}</uni-td>
</uni-tr>
</uni-table>
</view>
</view>
...
methods: {
//表单提交数据
submit(ref) {
this.$refs[ref].validate().then(res => {
this.$http('xag/plate_list', this.baseFormData, {
hideLoading: false,
hideMsg: false,
method: 'POST'
}).then(res => {
console.log("内容:", res.data)
if (res.data && res.data.length > 0) {
this.$tip.success("查询成功!")
this.result = res.data
} else {
this.$tip.success("查询结果为空,请换一个关键字查询!")
}
}).catch(err => {
console.log("产生异常,异常信息:", err)
})
}).catch(err => {
console.log('err', err);
})
}
...
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
最后部署完项目后,在前端页面就能根据板块名选择合适的个股进行投资了
4. 总结一下
由于行业板块更适用于中长期投资,我们只需要根据某一个关键字筛选出一个板块,然后在板块下的个股列表中可以非常直观地看出市盈率较低的个股进行投资即可。