Это был солнечный день, я начинаю с первой жизни в фонд, с тех пор курицу 🐔 упал в безнадежный пропасть, то я на самом деле наивно и добавил несколько ударов, до сих пор эту яму не заполнили ...
«Пришло время использовать немного силы печати.» Я стиснул свой помятый и сморщенный кошелек, взял большой меч узла, начал с деревни новичков, убил дракона ... о нет, путешествие убийства цыплят начал медленно.
просить
Я осведомился о старейшинах деревни по фамилии «ван» и, наконец, получил 3 свитка с жизненно важной информацией, с ними я могу мельком взглянутьКуриная эссенция странався картина.
-
Часть 1——
Вот кодовый номер каждой курицы и название, которое меня потрясло, когда я его услышал.Это действительно страна куриной эссенции.Когда я ее заказываю, там более 7000 куриных ртов.户口卷轴
:fund.east money.com/all fund.htm… -
Часть 2——
Этот свиток потрясающий.Измените кодовый номер в конце адреса и вы сможете увидеть базовый файл соответствующей курицы.档案卷轴
:fund.eastmoney.com/f10/000001
.html -
Часть 3——
Неожиданно сила этого свитка действительно властна, и это по-прежнему динамический свиток, изменяющий код в адресном заклинании.M卷轴
:fund.east money.com/ волосы 10 / F10data ...code
,Дата началаsdate
,крайний срокedate
и количество страницper
, он может показать график жизни этой курицы, толстая она или худая, счастливая или несчастная...
Пока что карта страны куриной сущности полностью собрана в моем сердце.
упражняться
Древние свитки дали мне достаточно подсказок, и я знаю ауру главного героя этой легенды, поэтому я призвал маленького мифического зверя, живущего в джунглях V8, без необходимости использования печатей——рептилия,имеютnodeРод быстрый и имеет острое обоняние, если дать ему куриное перо, то он может помочь мне найти курятник, но если вы хотите пересечь всю страну куриной эссенции, ее нужно дрессировать.
Во-первых, я должен получить следующее оборудование, чтобы гусеницы, куры и люди могли нормально общаться.
const express = require('express'); //搭建服务
const events = require('events'); //事件监听
const request = require('request'); //发送请求
const iconv = require('iconv-lite'); //网页解码
const cheerio = require('cheerio'); //网页解析
const MongoClient = require('mongodb').MongoClient; //数据库
const app = express(); //服务端实例
const Event = new events.EventEmitter(); //事件监听实例
const dbUrl = "mongodb://localhost:27017/"; //数据库连接地址
Я дал этому милому зверьку вульгарное имя:FundSpider
, придавая ему инкапсулированный обонятельный усилительfetch
:
// 基金爬虫
class FundSpider {
// 数据库名,表名,并发片段数量
constructor(dbName='fund', collectionName='fundData', fragmentSize=1000) {
this.dbUrl = "mongodb://localhost:27017/";
this.dbName = dbName;
this.collectionName = collectionName;
this.fragmentSize = fragmentSize;
}
// 获取url对应网址内容,除utf-8外,需指定网页编码
fetch(url, coding, callback) {
request({url: url, encoding : null}, (error, response, body) => {
let _body = coding==="utf-8" ? body : iconv.decode(body, coding);
if (!error && response.statusCode === 200){
// 将请求到的网页装载到jquery选择器中
callback(null, cheerio.load('<body>'+_body+'</body>'));
}else{
callback(error, cheerio.load('<body></body>'));
}
});
}
}
Теперь кодовый номер каждого куриного сита:
// 批量获取所有的基金代码
fetchFundCodes(callback) {
let url = "http://fund.eastmoney.com/allfund.html";
// 原网页编码是gb2312,需对应解码
this.fetch(url, 'gb2312', (err, $) => {
let fundCodesArray = [];
if(!err){
$("body").find('.num_right').find("li").each((i, item)=>{
let codeItem = $(item);
let codeAndName = $(codeItem.find("a")[0]).text();
let codeAndNameArr = codeAndName.split(")");
let code = codeAndNameArr[0].substr(1);
let fundName = codeAndNameArr[1];
if(code){
fundCodesArray.push(code);
}
});
}
callback(err, fundCodesArray);
});
}
Затем создайте оборудование позиционирования и отслеживания для краулера, и вы можете найти его файл по коду курицы:
// 根据基金代码获取对应基本信息
fetchFundInfo(code, callback){
let fundUrl = "http://fund.eastmoney.com/f10/" + code + ".html";
let fundData = {fundCode: code};
this.fetch(fundUrl,"utf-8", (err, $) => {
if(!err){
let dataRow = $("body").find(".detail .box").find("tr");
fundData.fundName = $($(dataRow[0]).find("td")[0]).text();//基金全称
fundData.fundNameShort = $($(dataRow[0]).find("td")[1]).text();//基金简称
fundData.fundType = $($(dataRow[1]).find("td")[1]).text();//基金类型
fundData.releaseDate = $($(dataRow[2]).find("td")[0]).text();//发行日期
fundData.buildDate = $($(dataRow[2]).find("td")[1]).text();//成立日期/规模
fundData.assetScale = $($(dataRow[3]).find("td")[0]).text();//资产规模
fundData.shareScale = $($(dataRow[3]).find("td")[1]).text();//份额规模
fundData.administrator = $($(dataRow[4]).find("td")[0]).text();//基金管理人
fundData.custodian = $($(dataRow[4]).find("td")[1]).text();//基金托管人
fundData.manager = $($(dataRow[5]).find("td")[0]).text();//基金经理人
fundData.bonus = $($(dataRow[5]).find("td")[1]).text();//分红
fundData.managementRate = $($(dataRow[6]).find("td")[0]).text();//管理费率
fundData. trusteeshipRate = $($(dataRow[6]).find("td")[1]).text();//托管费率
fundData.saleServiceRate = $($(dataRow[7]).find("td")[0]).text();//销售服务费率
fundData.subscriptionRate = $($(dataRow[7]).find("td")[1]).text();//最高认购费率
}
callback(err, fundData);
});
}
Информация, полученная выше, почти не изменилась с момента основания Королевства Цзицзин, хотя они и стали сущностями после основания страны. Если мне придется каждый раз вызывать краулера, когда я хочу полистать архивы и заставлять его повторять работу, боюсь, что затрат на еду не хватит. К счастью, я получил копию в пакете для начинающих.MongoDB
Сундук с сокровищами, с возможностью свободного доступа, затем сохранения всех этих файлов, и чтения их в будущем.
В процессе обучения рептилии начали отслеживать их одновременно. Я обнаружил, что после проверки более 7000 цыплят за один раз всегда будет отсутствовать около одной трети цыплят. время, чтобы иметь нового партнера присоединиться.
// 并发控制器,控制单次并发调用的数量
class ConcurrentCtrl {
// 调用者上下文环境,并发分段数量(建议不要超过1000),调用函数,总参数数组,数据库表名
constructor(parent, splitNum, fn, dataArray=[], collection){
this.parent = parent;
this.splitNum = splitNum;
this.fn = fn;
this.dataArray = dataArray;
this.length = dataArray.length; // 总次数
this.itemNum = Math.ceil(this.length/splitNum); // 分段段数
this.restNum = (this.length%splitNum)===0 ? splitNum : (this.length%splitNum); // 最后一次分段的余下次数
this.collection = collection;
}
// go(0)启动调用,循环计数中达到分段数量便进行下一次片段并发
go(index) {
if((index%this.splitNum) === 0){
if(index/this.splitNum !== (this.itemNum-1)){
this.fn.call(this.parent, this.collection, this.dataArray.slice(index,index+this.splitNum));
}else{
this.fn.call(this.parent, this.collection, this.dataArray.slice(index,index+this.restNum));
}
}
}
}
С его помощью будет идеальным ритмом контролировать параллелизм каждого действия краулера примерно до 1000; затем научите краулер автоматически помещать файлы сущности курицы, которые он охотится, в сокровищницу MongoDB, от мала до велика, сначала специально сообщите сканеру, что делать после каждого параллельного отслеживания.
// 并发获取的基金信息片段保存到数据库指定的表
fundFragmentSave(collection, codesArray){
for (let i = 0; i < codesArray.length; i++) {
this.fetchFundInfo(codesArray[i], (error, fundData) => {
if(error){
Event.emit("error_fundItem", codesArray[i]);
Event.emit("fundItem", codesArray[i]);
}else{
// 指定每条数据的唯一标志是基金代码,便于查询与排序
fundData["_id"] = fundData.fundCode;
collection.save(fundData, (err, res) => {
Event.emit("correct_fundItem", codesArray[i]);
Event.emit("fundItem", codesArray[i]);
if (err) throw err;
});
}
});
}
}
Таким образом, сканер учится сообщать о ситуации в любое время в процессе отслеживания.fundItem
сигнал, который будет выдан при ошибке или успехе соответственно.error_fundItem
а такжеcorrect_fundItem
сигнал о.
Далее сотрудничать с новыми партнерамиConcurrentCtrl
, просто сообщите сканеру набор кодовых чисел для отслеживанияcodesArray
, поймать цыпленка за тысячи миль — дело мимолетное:
// 并发获取给定基金代码数组中对应的基金基本信息,并保存到数据库
fundToSave(error, codesArray=[]){
if(!error){
let codesLength = codesArray.length;
let itemNum = 0; // 已爬过的数量
let errorItems = []; // 爬取失败的基金代码数组
let errorItemNum = 0; // 爬取失败的基金代码数量
let correctItems = []; // 爬取成功的基金代码数组
let correctItemNum = 0; // 爬取成功的基金代码数量
console.log(`基金代码共计 ${codesLength} 个`);
// 数据库连接
MongoClient.connect(this.dbUrl, (err, db) => {
if (err) throw err;
// 数据库实例
let fundDB = db.db(this.dbName);
// 数据表实例
let dbCollection = fundDB.collection(this.collectionName);
// 并发控制器实例
let concurrentCtrl = new ConcurrentCtrl(this, this.fragmentSize, this.fundFragmentSave, codesArray, dbCollection);
// 事件监听
Event.on("fundItem", (_code) => {
// 计数
itemNum++;
console.log(`index: ${itemNum} --- code: ${_code}`);
// 并发控制
concurrentCtrl.go(itemNum);
// 所有基金信息爬取完毕
if (itemNum >= codesLength) {
console.log("save finished");
if(errorItems.length > 0){
console.log("---error code----");
console.log(errorItems);
}
// 关闭数据库
db.close();
}
});
Event.on("error_fundItem", (_code) => {
errorItems.push(_code);
errorItemNum++;
console.log(`error index: ${errorItemNum} --- error code: ${_code}`);
});
Event.on("correct_fundItem", (_code) => {
correctItemNum++;
});
// 片段式并发启动
concurrentCtrl.go(0);
});
}else{
console.log("fundToSave error");
}
}
Тогда метод отлова цыплят можно считать практикой.Макро может посмотреть в национальных регистрационных карточках куриной эссенции, и мы можем легко взять несколько и убить их невидимо:
// 未传参则获取所有基金基本信息,给定基金代码数组则获取对应信息,均更新到数据库
fundSave(_codesArray){
if(!_codesArray){
// 所有基金信息爬取保存
this.fetchFundCodes((err, codesArray) => {
this.fundToSave(err, codesArray);
})
}else{
// 过滤可能的非数组入参的情况
_codesArray = Object.prototype.toString.call(_codesArray)==='[object Array]' ? _codesArray : [];
if(_codesArray.length > 0){
// 部分基金信息爬取保存
this.fundToSave(null, _codesArray);
}else{
console.log("not enough codes to fetch");
}
}
}
Как начать? Заклинание следующее, но не забудьте поставитьMongoDB
Крышка сундука с сокровищами открывается.
let fundSpider = new FundSpider("fund","fundData",1000);
// 更新保存全部基金基本信息
fundSpider.fundSave();
// 更新保存代码为000001和040008的基金的基本信息
// fundSpider.fundSave(['000001','040008']);
Давай, Пикачу! Я наблюдал, как краулер отделил 1000 призраков, а затем исчез одновременно со свистом. Когда я помедитировал 10 секунд и открыл сундук с сокровищами MongoDB, я увидел следующую ситуацию:
Я рассмеялся и, наконец, рассказал мне все подробности о вас, цыплятах! а ха ха ха!
Эх, подождите, даже если я знаю возраст, происхождение и недвижимость каждого цыпленка, цыплят в мире нельзя убить, особенно куриную эссенцию Какой прок от этого железного прута? Что делать, если я хочу этот файл? ( ˙-˙ ) Все еще беспокойно, все еще тревожно...
Мне нужно:定向杀鸡
Чуть не забыл про третий динамический скролл:M卷轴
, с его силой можно узнать, полная ли курица, толстая или худая, хорошо ли ее ловить. Кажется, что рептилиям нужно немного больше навыков.
// 日期转字符串
getDateStr(dd){
let y = dd.getFullYear();
let m = (dd.getMonth()+1)<10 ? "0"+(dd.getMonth()+1) : (dd.getMonth()+1);
let d = dd.getDate()<10 ? "0"+dd.getDate() : dd.getDate();
return y + "-" + m + "-" + d;
}
// 爬取并解析基金的单位净值,增长率等信息
fetchFundUrl(url, callback){
this.fetch(url, 'gb2312', (err, $)=>{
let fundData = [];
if(!err){
let table = $('body').find("table");
let tbody = table.find("tbody");
try{
tbody.find("tr").each((i,trItem)=>{
let fundItem = {};
let tdArray = $(trItem).find("td").map((j, tdItem)=>{
return $(tdItem);
});
fundItem.date = tdArray[0].text(); // 净值日期
fundItem.unitNet = tdArray[1].text(); // 单位净值
fundItem.accumulatedNet = tdArray[2].text(); // 累计净值
fundItem.changePercent = tdArray[3].text(); // 日增长率
fundData.push(fundItem);
});
callback(err, fundData);
}catch(e){
console.log(e);
callback(e, []);
}
}
});
}
// 根据基金代码获取其选定日期范围内的基金变动数据
// 基金代码,开始日期,截止日期,数据个数,回调函数
fetchFundData(code, sdate, edate, per=9999, callback){
let fundUrl = "http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz";
let date = new Date();
let dateNow = new Date();
// 默认开始时间为当前日期的3年前
sdate = sdate?sdate:this.getDateStr(new Date(date.setFullYear(date.getFullYear()-3)));
edate = edate?edate:this.getDateStr(dateNow);
fundUrl += ("&code="+code+"&sdate="+sdate+"&edate="+edate+"&per="+per);
console.log(fundUrl);
this.fetchFundUrl(fundUrl, callback);
}
Используйте следующим образом:
let fundSpider = new FundSpider();
fundSpider.fetchFundData('040008', '2018-03-20', '2018-05-04', 30, (err, data) => {
console.log(data);
});
Путь совершенствования толст и узок Я собрал все, что мне нужно о Цзицзинго, в три вечных жемчужины:
// 所有基金代码查询接口
app.get('/fetchFundCodes', (req, res) => {
let fundSpider = new FundSpider();
res.header("Access-Control-Allow-Origin", "*");
fundSpider.fetchFundCodes((err, data)=>{
res.send(data.toString());
});
});
// 根据代码查询基金档案接口
app.get('/fetchFundInfo/:code', (req, res) => {
let fundSpider = new FundSpider();
res.header("Access-Control-Allow-Origin", "*");
fundSpider.fetchFundInfo(req.params.code, (err, data) => {
res.send(JSON.stringify(data));
});
});
// 基金净值变动情况数据接口
app.get('/fetchFundData/:code/:per', (req, res) => {
let fundSpider = new FundSpider();
res.header("Access-Control-Allow-Origin", "*");
fundSpider.fetchFundData(req.params.code, undefined, undefined, req.params.per, (err, data) => {
res.send(JSON.stringify(data));
});
});
app.listen(1234,()=>{
console.log("service start on port 1234");
});
дуэль
Я попал под город Цзицзинго, и драгоценные камни, только что вставленные в узловой меч, сияли под солнечным светом. Я направил свой меч на городские ворота и громко закричал:
«Всем вам, о нет, пора умереть за ваших цыплят!»
Куриная эссенция появится в городе, он только что увидел драгоценный камень на моем мече, но сказал холодно и холодно:
«Хм, то, что вы можете видеть, это только эти холодные данные, даже если вы поставите перед собой 100 цыплят, даже если вы выдернете все волоски и дадите вам час, просто основываясь на этих числах, я не думаю, что вы можете найти то, что вы хотите.
Неожиданно этот ров действительно сделал то, что в нем написано: он открыл городские ворота и позволил сотне куриц стоять в десяти метрах от меня без всякого страха.
Громкое кукареканье заставило меня немного смутиться, но если это было так, как он сказал, я посмотрел на этих цыплят, которые были почти такими же тонкими, как перья, и пот начал падать на мой лоб, но меч, поднятый в воздух, не не смей падать.
«Проходя мимо и увидев тебя в беде, я дам тебе в помощь сокровище».
Внезапно рядом со мной раздался густой голос. Это оказался старик. Я отнесся к этой штуке с подозрением. Это была чешуйка серебра с чрезвычайно гладкой поверхностью. Что? Это оказалось двусторонней фольгой! Двусторонняя фольга, которая может наносить перемешанные числа на двумерную диаграмму! Такой артефакт меня очень радует.
— Могу я узнать фамилию и имя старца!
"ECharles~”
Звук не исчез, но человек ушел.
Я осторожно бросил Erxiang Foil в центр городских ворот, и в одно мгновение стало так тихо, что ров заморозил его ошеломленные глаза на месте, в то время как другие куриные эссенции были похожи на кусочки бумаги, лежащие на городской стене.
// 基金数据可视化(前端代码)
const React = require("react");
const Echarts = require("echarts");
const EcStat = require("echarts-stat");
const fetch = require("isomorphic-unfetch");
class FundChart extends React.Component{
constructor(props) {
super(props);
// 按钮切换标志
this.state = {
switchIndex: 1
}
}
// 获取基金档案
fetchFundInfo(code, callback) {
return fetch(`http://localhost:1234/fetchFundInfo/${code}`).then((res) => {
res.json().then((data) => {
callback(data);
})
}).catch((err) => {
console.log(err);
});
}
// 获取基金净值变动数据
fetchFundData(code, per, callback) {
return fetch(`http://localhost:1234/fetchFundData/${code}/${per.toString()}`).then((res) => {
res.text().then((data) => {
callback(JSON.parse(data));
})
}).catch((err) => {
console.log(err);
});
}
// 获取ECharts绘制的数据
getChart(fundData) {
// 起始点净值
let startUnitNet = parseFloat(fundData[0].unitNet);
// 计算其他时间点净值与起始点净值的相对百分比
// 日期为横坐标,净值为纵坐标
let data = fundData.map(function(item) {
return [item.date, parseFloat((100.0 * ((parseFloat(item.unitNet) - startUnitNet) / startUnitNet)).toFixed(2))]
});
// 取数组下标为横坐标,净值为纵坐标,用于散点图与回归分析
let dataRegression = data.map(function(item, i) {
return [i, item[1]];
});
// 折线图横坐标数组
let dateList = data.map(function(item) {
return item[0];
});
// 折线图纵坐标数组
let valueList = data.map(function(item) {
return item[1];
});
// 计算线性回归
let myRegression = EcStat.regression('linear', dataRegression);
// 线性回归的的散点排序
myRegression.points.sort(function(a, b) {
return a[0] - b[0];
});
// 线性回归后的拟合方程y=Kx+B
let K = myRegression.parameter.gradient;
let B = myRegression.parameter.intercept;
let optionFold = {
title: [{
left: 'center',
}],
tooltip: {
trigger: 'axis'
},
xAxis: [{
data: dateList
}],
yAxis: [{
splitLine: {
show: false
}
}],
series: [{
type: 'line',
showSymbol: false,
data: valueList,
itemStyle: {
color: '#3385ff'
}
}]
};
let optionRegression = {
title: {
subtext: 'linear regression',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
xAxis: {
type: 'value',
splitLine: {
lineStyle: {
type: 'dashed'
}
},
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: {
type: 'dashed'
}
},
},
series: [{
name: 'scatter',
type: 'scatter',
itemStyle: {
color: '#3385ff'
},
label: {
emphasis: {
show: true,
position: 'left'
}
},
data: dataRegression
}, {
name: 'line',
type: 'line',
showSymbol: false,
data: myRegression.points,
markPoint: {
itemStyle: {
normal: {
color: 'transparent'
}
},
label: {
normal: {
show: true,
position: 'left',
formatter: myRegression.expression,
textStyle: {
color: '#333',
fontSize: 14
}
}
},
data: [{
coord: myRegression.points[myRegression.points.length - 1]
}]
}
}]
};
return {
optionFold: optionFold,
optionRegression: optionRegression,
regression: myRegression,
K: K,
B: B
}
}
// 绘制图表
drawChart(fundData, fundInfo) {
if (!this.chartFold) {
this.chartFold = Echarts.init(document.getElementById('chart_fold'));
}
if (!this.chartPoints) {
this.chartPoints = Echarts.init(document.getElementById('chart_points'));
}
if (fundData && (fundData.length > 0)) {
// 更新图表绘制
let chartObj = this.getChart(fundData);
this.chartFold.setOption(chartObj.optionFold);
this.chartPoints.setOption(chartObj.optionRegression);
} else {
// 更新图表标题
this.chartFold.setOption({
title: {
text: fundInfo.fundNameShort
}
});
this.chartPoints.setOption({
title: {
text: fundInfo.fundNameShort
}
});
}
}
// 时间范围按钮切换
dateSwitch(index, per) {
this.setState({
switchIndex: index
}, () => {
this.fetchFundData(this.props.code, per, (data) => {
this.drawChart(data.reverse());
});
});
}
// 时间范围按钮
getSwitchBtns() {
let switchArray = [
['最近一周', 7],
['最近一月', 30],
['最近3月', 90],
['最近半年', 180],
['最近一年', 365],
['最近三年', 1095]
];
let switchIndex = this.state.switchIndex;
return (
<div>
{switchArray.map((item, i)=>{
let active = (i==switchIndex ? true : false);
let label = item[0];
let per = item[1];
return (<button className={"switch-btn"+(active?" active":"")} onClick={this.dateSwitch.bind(this,i,per)}>{label}</button>)
})}
</div>
)
}
componentDidMount() {
// 默认加载最近一月的基金数据
this.fetchFundData(this.props.code, 30, (data) => {
this.drawChart(data.reverse());
});
// 基金标题获取
this.fetchFundInfo(this.props.code, (data) => {
console.log(data);
this.drawChart([], data);
});
}
render() {
return (
<div className="fundChart-container">
<div id="chartbox" className="chart-box">
<div className="chart-fold" id="chart_fold"></div>
<div className="chart-points" id="chart_points"></div>
</div>
<div className="switch-box">
{this.getSwitchBtns()}
</div>
</div>
);
}
}
«Покупайте дешево и не покупайте дорого, и вы должны преуспевать, когда покупаете дно!»
Пока я выкрикивал формулу, я махал большим мечом, и многие цыплята были разрублены мною на куски и рассеяны в воздухе.
Мои глаза подобны дракону, когда враг пуст, моя тактика бесконечна, мое наступление подобно ветру, и я вхожу во дворец с мечом.
Ведь меня остановили, а напротив был сильный генерал из Цзицзинго, полный маны и свирепый, что заставляло меня шаг за шагом отступать.
Я схватился за грудь и сопротивлялся запаху крови, который, казалось, хлынул из моего живота:
— Осмелишься... осмелишься спросить твое имя?
«Я Великий Жрец Куриной Сущности, древней кожаной куртки!»
Это старинная кожаная куртка! Легендарная старинная кожаная куртка великого жреца, который всегда покрывал страну куриной сущностью! Говорят, что король королевства Цзицзин существует только по имени, и он является монопольной властью древней кожаной куртки, он гений, владеющий силой судьбы и являющийся флюгером всей страны!
«В мире есть много вещей, которые ты не можешь понять»
Гу Pipao сказал презрительно.
«Ты не первый незваный гость, погибший от моих рук, но из-за моего сострадания, в память о тебе, я дал тебе титул».
"Какое имя...?"
Я едва поддерживал свое тело, но мое любопытство все же заставило меня спросить.
"китайский лук"
Как только он закончил говорить, он взмахнул серпом и подошел.До того, как мой мир погрузился в полную тишину, я мог видеть только равнодушную улыбку на его лице.
初入掘金第二篇文章,写着写着发现自己编起了段子...感觉标题应该改为“韭菜传”?总之,瞎编不易,转载烦请注明出处,铭谢~
-----------优柔寡断的分割线---------
鉴于有评论里少侠中意源码,双手奉上我稚嫩的github地址:https://github.com/youngdro/fundSpider,少侠们有空可否顺便戳一戳那颗buling buling的星星✨,后续我慢慢把其他库存货往这上面挪吧(我怕是一个假程序员...)