diff --git a/.gitignore b/.gitignore index 60b1d282..90ef979d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ cookbook/appworld/data/* cookbook/appworld/experiments/* cookbook/appworld/exp_result/* file_vector_store/* -cookbook/appworld/file_vector_store/* \ No newline at end of file +cookbook/appworld/file_vector_store/* +experiencemaker/tool/web_search_cach/* \ No newline at end of file diff --git a/experiencemaker/__init__.py b/experiencemaker/__init__.py index 78cd33c9..9ce0fe86 100644 --- a/experiencemaker/__init__.py +++ b/experiencemaker/__init__.py @@ -1,4 +1,4 @@ -from .app import main +# from .app import main __version__ = "0.1.1" diff --git a/experiencemaker/llm/openai_compatible_llm.py b/experiencemaker/llm/openai_compatible_llm.py index cb4a952d..a004ca78 100644 --- a/experiencemaker/llm/openai_compatible_llm.py +++ b/experiencemaker/llm/openai_compatible_llm.py @@ -31,7 +31,7 @@ class OpenAICompatibleBaseLLM(BaseLLM): # API configuration api_key: str = Field(default_factory=lambda: os.getenv("LLM_API_KEY"), description="API key for authentication") base_url: str = Field(default_factory=lambda: os.getenv("LLM_BASE_URL"), description="Base URL for the API endpoint") - _client: OpenAI = PrivateAttr(description="OpenAI client instance (private)") + _client: OpenAI = PrivateAttr() @model_validator(mode="after") def init_client(self): diff --git a/experiencemaker/op/react/react_v1_op.py b/experiencemaker/op/react/react_v1_op.py index 7a1e8d1d..49ab8540 100644 --- a/experiencemaker/op/react/react_v1_op.py +++ b/experiencemaker/op/react/react_v1_op.py @@ -23,7 +23,8 @@ class ReactV1Op(BaseOp): response: AgentResponse = self.context.response max_steps: int = int(self.op_params.get("max_steps", 10)) - tool_names = self.op_params.get("tool_names", "code_tool,dashscope_search_tool,terminate_tool") + # dashscope_search_tool tavily_search_tool + tool_names = self.op_params.get("tool_names", "code_tool,tavily_search_tool,terminate_tool") tools: List[BaseTool] = [TOOL_REGISTRY[x.strip()]() for x in tool_names.split(",") if x] tool_dict: Dict[str, BaseTool] = {x.name: x for x in tools} now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') diff --git a/experiencemaker/tool/__init__.py b/experiencemaker/tool/__init__.py index 803a16e5..c10d8c6d 100644 --- a/experiencemaker/tool/__init__.py +++ b/experiencemaker/tool/__init__.py @@ -4,5 +4,6 @@ TOOL_REGISTRY = Registry() from experiencemaker.tool.code_tool import CodeTool from experiencemaker.tool.dashscope_search_tool import DashscopeSearchTool +from experiencemaker.tool.tavily_search_tool import TavilySearchTool from experiencemaker.tool.terminate_tool import TerminateTool from experiencemaker.tool.mcp_tool import MCPTool diff --git a/experiencemaker/tool/tavily_search_tool.py b/experiencemaker/tool/tavily_search_tool.py new file mode 100644 index 00000000..0236c583 --- /dev/null +++ b/experiencemaker/tool/tavily_search_tool.py @@ -0,0 +1,109 @@ +import json +import os +import re +import time +from typing import Literal + +from loguru import logger +from pydantic import Field, model_validator, PrivateAttr +from tavily import TavilyClient + +from experiencemaker.tool import TOOL_REGISTRY +from experiencemaker.tool.base_tool import BaseTool + + +@TOOL_REGISTRY.register() +class TavilySearchTool(BaseTool): + name: str = "web_search" + description: str = "Use query to retrieve relevant information from the internet." + parameters: dict = { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "search query", + } + }, + "required": ["query"] + } + enable_print: bool = Field(default=True) + enable_cache: bool = Field(default=False) + cache_path: str = Field(default="./web_search_cache") + topic: Literal["general", "news", "finance"] = Field(default="general") + + _client: TavilyClient | None = PrivateAttr() + + @model_validator(mode="after") + def init(self): + if not os.path.exists(self.cache_path): + os.makedirs(self.cache_path) + + self._client = TavilyClient() + return self + + def load_cache(self, cache_name: str = "default") -> dict: + cache_file = os.path.join(self.cache_path, cache_name + ".jsonl") + if not os.path.exists(cache_file): + return {} + + with open(cache_file) as f: + return json.load(f) + + def dump_cache(self, cache_dict: dict, cache_name: str = "default"): + cache_file = os.path.join(self.cache_path, cache_name + ".jsonl") + with open(cache_file, "w") as f: + return json.dump(cache_dict, f, indent=2, ensure_ascii=False) + + @staticmethod + def remove_urls_and_images(text): + pattern = re.compile(r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]') + result = pattern.sub("", text) + return result + + def post_process(self, response): + if self.enable_print: + logger.info("response=\n" + json.dumps(response, indent=2, ensure_ascii=False)) + + return response + + def execute(self, query: str = "", **kwargs): + assert query, "Query cannot be empty" + + cache_dict = {} + if self.enable_cache: + cache_dict = self.load_cache() + if query in cache_dict: + return self.post_process(cache_dict[query]) + + for i in range(self.max_retries): + try: + response = self._client.search(query=query, topic=self.topic) + url_info_dict = {item["url"]: item for item in response["results"]} + response_extract = self._client.extract(urls=[item["url"] for item in response["results"]], + format="text") + + final_result = {} + for item in response_extract["results"]: + url = item["url"] + final_result[url] = url_info_dict[url] + final_result[url]["raw_content"] = item["raw_content"] + + if self.enable_cache: + cache_dict[query] = final_result + self.dump_cache(cache_dict) + + return self.post_process(final_result) + + except Exception as e: + logger.exception(f"tavily search with query={query} encounter error with e={e.args}") + time.sleep(i + 1) + + return None + + +if __name__ == "__main__": + from dotenv import load_dotenv + + load_dotenv() + tool = TavilySearchTool() + tool.execute(query="恒生医药为什么一直涨") diff --git a/experiencemaker/tool/web_search_cache/default.jsonl b/experiencemaker/tool/web_search_cache/default.jsonl new file mode 100644 index 00000000..d6db8afd --- /dev/null +++ b/experiencemaker/tool/web_search_cache/default.jsonl @@ -0,0 +1,46 @@ +{ + "恒生医药为什么一直涨": { + "query": "恒生医药为什么一直涨", + "follow_up_questions": null, + "answer": "恒生医药持续涨价,因政策支持和市场乐观情绪推动。创新药市场增长和资本流入增强了信心。ETF持续表现优异。", + "images": [], + "results": [ + { + "url": "https://finance.sina.com.cn/stock/marketresearch/2024-05-09/doc-inaurxyc6090539.shtml", + "title": "为什么说恒生医药进入中期布局拐点? - 新浪财经", + "content": "受益于“五一”期间中国经济复苏验证的乐观判断,全球资本加速流入香港市场,港股科技板块全线拉升。恒生科技指数(HSTECH.HI)再度拉涨4.45%,恒生香港上市生物", + "score": 0.69573146, + "raw_content": "![](//beacon.sina.com.cn/a.gif?noScript)\n![](//i.sso.sina.com.cn/images/login/thumb_default.png)\n![](//n.sinaimg.cn/finance/c30320b4/20190809/cj_sinafinance_app2x.png)\n\n# 为什么说恒生医药进入中期布局拐点?\n\n  炒股就看[金麒麟分析师研报](https://finance.sina.com.cn/stock/reportch/jinqilin.shtml),权威,专业,及时,全面,助您挖掘潜力主题机会!\n\n  来源:券业行家\n\n  为什么说恒生医药进入中期布局拐点?\n\n  “五一”前后,中国资产成了全球资金的热门“打卡”板块,港股、中概股领跑全球。\n\n  转折点在4月19日周五晚间,中国证监会发布5项资本市场对港合作措施,旨在拓展优化沪深港通机制,助力香港巩固提升国际金融中心地位。次周一4月22日起,港股主要指数、美股中概股连续逼空大涨。4月23日,瑞银将中国大陆和中国香港股票评级上调至超配,为近年来外资大行罕见唱多中国资产。\n\n  5月2日,中资股行情引爆。\n\n  受益于“五一”期间中国经济复苏验证的乐观判断,全球资本加速流入香港市场,港股科技板块全线拉升。恒生科技指数(HSTECH.HI)再度拉涨4.45%,恒生香港上市生物科技指数(HSHKBIO.HI)更是暴涨4.7%,美股中概股指数(US36966)也大涨6.96%。从4月22日到5月6日,恒生指数罕见连续10个交易日收涨,累计上涨14.51%,颇有“技术性牛市”之势。\n\n![](//n.sinaimg.cn/finance/crawl/754/w426h328/20240509/84c0-d8d8c6af5849085cf9a17c44b2570474.jpg)\n\n  香港市场核心指数表现(数据来源:Wind)\n\n  恒生香港上市生物科技指数是港股生物制药、基因测序、细胞治疗等领域,高科技、高成长、高弹性的代表指数,成份股包括在香港交易所透过上市规则第18A章上市的公司,创新药、CXO等前沿科技含量尤为突出。此间其在港股核心指数中一枝独秀的表现,表明全球资金在风险偏好拔升时对港股医药生物板块的特别垂青。\n\n  这是一个值得高度关注的新动向,港股医药生物板块或迎来中期布局的拐点窗口。\n\n![](//n.sinaimg.cn/finance/crawl/32/w500h332/20240509/5fc3-24bbcfc1f3d3fe696ccf55cdf7258443.jpg)\n\n  三年震荡调整或见底\n\n  复盘看,恒生香港上市生物科技指数于2019å¹´12月16日发布。2021å¹´6月29日摸顶历史最高点2925.74点,到2024å¹´4月19日触底最低点712.69点。期间历经近三年巨幅震荡调整,历史极点的最大调整幅度高达75.64%。\n\n![](//n.sinaimg.cn/finance/crawl/163/w550h413/20240509/0232-75f199b6ba2475a02693ea5d7d129f07.jpg)\n\n  (数据来源:Wind,时间:2019.12.16~2024.5.8)\n\n  港股生物科技指数发布时正值新冠疫情爆发,生物医药板块在需求推动下持续拉高估值,指数自发布日起,短短一年半时间内,到2021å¹´6月28日收盘价历史最高日时,足足上涨了96.54%。基本面价值在较短时间内被提前透支是指数见顶的重要原因。\n\n  见顶深跌的外部推动因素,则是美国对中美科技交流的政策转向。2021å¹´5月12日,美国国会参议院商务委员会通过“无尽前沿法案”,批准在5年内拨款1100多亿美元用于投资人工智能、半导体、量子计算、先进通信、生物技术和先进能源等关键技术领域的基础和高级研究、商业化以及教育和培训计划,以应对来自中国日益严重的竞争压力。很快,6月8日,美参议院通过升级版的《2021美国创新和竞争法案》,投资金额提升到2500亿美元。\n\n  自高点向下,生物科技指数持续单边下挫近一年时间才开启震荡整理。2024å¹´1月,美国再提《生物安全法案》,中国创新药、CXO企业再遭冲击,生物科技指数开始逐步触底。\n\n  Wind数据显示,截至2024å¹´5月8日,恒生香港上市生物科技指数在连续反弹之后,市盈率TTM也仅为28.64倍,处于历史分位点2.59%的极低位置,远低于历史中位值的90.9倍、最大值的2126.3倍;市净率1.72倍,处于历史分位点3.93%;市销率0.85倍,处于历史分位点3.49%,修复空间巨大,当前估值性价比、安全边际均较高。\n\n![](//n.sinaimg.cn/finance/crawl/32/w500h332/20240509/5fc3-24bbcfc1f3d3fe696ccf55cdf7258443.jpg)\n\n  创新药获全方位支持\n\n  在市场情绪转暖时,医药生物方向受到资金偏好的大逻辑是,老龄化加速背景下,医疗开支在需求端是较为刚性的。4月下旬,针对沪深港市场生物科技、医药板块行情,上海证券分析,需求端和供给端6大因素将共同推动消费医疗市场增长。\n\n  需求端:(1)政策红利释放。“健康中国2030”健全全民健康制度体系,推进以“治病为中心”向“以健康为中心”转变。(2)支付能力提升。对标发达国家经验,中国健康产业支付能力仍有较大提升空间。(3)人口结构变化。随着老龄化加速,具有更强经济实力与教育水平的“60后”步入退休潮,医疗健康迎来旺盛需求。(4)后疫情时代更加关注健康。大众日常保健意识提升,让保养疗愈成为可持续的日常。\n\n  供给端:(1)产品转向创新驱动,功效提升且加速迭代,朝着多元化发展,满足消费者多样化需求。2023年保健食品备案3632款,同比增长52%。(2)数字化医疗生态布局日臻完善,有效链接健康消费群体,包括信息科普、远程问诊、线上购药、疾病管理。其中信息科普通过各类社交平台、搜索引擎等提供保健养生、疾病诊治等相关信息,渗透率大于60%。\n\n  近来,在外部压力下,创新药行业逐步酝酿全方位支持政策。今年3月“创新药”首次写入国务院政府工作报告,具体支持政策正渐次推出。4月7日,北京市医疗保障局等9部门制定《北京市支持创新医药高质量发展若干措施(2024)(征求意见稿)》。4月11日,国家医保局发布《2023年医疗保障事业发展统计快报》,通过建立覆盖申报、评审、测算、谈判等全流程的创新药支持机制,支持更多新药好药纳入医保,2023,年目录调整中,有,57,个药品实现了“当年获批、当年纳入目录”。\n\n  创新药作为新质生产力代表,“政策包”接续出台,医药产业迎来深刻变革,市场对医药板块、创新药产业的悲观预期也正在根本扭转。\n\n  事实上,2022å¹´~2023年全球药企管线规模排名前25名中有3,家中国企业上榜,表明我国在全球药物创新中崭露头角。“十四五”期间,我国创新药正积极实现高质量“引进来”和高水平“走出去”。2021å¹´~2022,年,中国创新药授权引进(license-in)交易项目多涉及CD分子、RNA药物等赛道。同时,[百济神州](https://finance.sina.com.cn/realstock/company/sh688235/nc.shtml)的泽布替尼实现了本土新药出海“零的突破”,且对外授权(license-out)也捷报频频。据医药魔方数据统计,2022,年,我国license-out交易总金额达277亿美元,较2020年增长122.08%。(来源:《[中国医药](https://finance.sina.com.cn/realstock/company/sh600056/nc.shtml)产业高质量发展状况调研(2021—2023)》研究报告综述,中国食品药品监管杂志2024年第三期)\n\n  [国金证券](https://finance.sina.com.cn/realstock/company/sh600109/nc.shtml)认为,全球创新药是科技进步兑现的重要领域,中国创新药产业链正在崛起;院内诊疗全面恢复,国家对创新药的鼓励支持政策陆续出台,以及中国药企国际化步伐的推进,行业整体将迎来业绩与市场表现的同步向上。[中银证券](https://finance.sina.com.cn/realstock/company/sh601696/nc.shtml)表示,医药生物行业伴随“医药反腐”影响消退和基数压力消化,后续业绩增速有望逐季改善,国内政策支持及创新药出海前景均值得期待。\n\n![](//n.sinaimg.cn/finance/crawl/32/w500h332/20240509/5fc3-24bbcfc1f3d3fe696ccf55cdf7258443.jpg)\n\n  美降息周期利好科技\n\n  从外部环境而言,近期港股持续上涨,或受美元降息周期来临、日本继续加息概率降低等影响,外资部分流向港股市场。\n\n  在港股5月投资策略中,[国信证券](https://finance.sina.com.cn/realstock/company/sz002736/nc.shtml)认为,港股有望孕育长期基本面修复行情。随着恒生科技多年来估值底部的确认,恒生指数、恒生港股通、恒生高股息、恒生科技的估值底部全部确认完毕,将打消一直以来“杀估值”的最大不确定性,有助于长线资金回流这些公司并开启长期基本面修复。\n\n  [国泰君安](https://finance.sina.com.cn/realstock/company/sh601211/nc.shtml)研报表示,港股在近期大幅走高后,会出现一定分歧和震荡,但随着中国在经济、政策领域不确定性降低,港股仍有可为空间。国内方面,经济政策以稳为主,地产政策持续优化,港股市场盈利预期改善,外资上修国内经济前景,港股市场情绪修复。海外方面,美联储呵护金融和劳动力市场的意图明显,市场流动性进一步收紧的风险下降。\n\n  而医药生物、创新药、CXO产业方向,除了关注基本面趋势,作为科技主线,估值还受到全球资金流动,尤其美元周期的深刻影响。近期,虽然美联储降息预期有所延后,但美元降息周期开启的判断并未改变,对科技创新主线的估值形成有利的全球流动性边际改善环境。\n\n  创新主线是穿越周期的法宝。生物科技是医药行业创新度高、成长空间大的主流赛道。恒生香港上市生物科技指数已基本完成政策底、基本面底、估值底、资金面底的共振,适合进行中长周期布局持有,分享医药生物高成长核心资产的复利增长。\n\n  近期份额持续增长、净值明显修复、场内价格持续反弹的恒生医药ETF(159892.SZ),正是跟踪恒生香港上市生物科技指数的高光品种,在跟踪同指数的ETF产品中,恒生医药ETF的基金规模、成交额最大,流动性良好且支持T+0交易。同时,恒生医药ETF也有对应的场外联接基金(A类:016970;C类:016971),费率在同类中属最低档水平,没有股票账户的场外投资者也可以关注。\n\n  值得一提的是,港股医药生物板块因上市制度差异,包含了一些未能在A股上市的优质稀缺的生物医药企业,恒生香港上市生物科技指数前十大成分股中绝大部分都没有在A股上市。这也是港股医药生物板块区别A股的核心资产优势,该指数前十大成分股涵盖各个细分领域的龙头企业,覆盖生物医药产业链上中下游各个细分环节。\n\n  总之,通过恒生医药ETF一揽子买入了在港上市的一流新老实力药企,在周期拐点布局或可拥有较强的穿越周期能力。\n\n![](//n.sinaimg.cn/finance/0/w400h400/20231127/9ece-7520e4e0c86423a0c596834c30eb42ea.png)\n![](//n.sinaimg.cn/finance/cece9e13/20200514/343233024.png)\n\n责任编辑:杨红卜\n\n![](//n.sinaimg.cn/finance/260/w880h180/20190724/5154-iafwsqp6192795.jpg)\n![](//n.sinaimg.cn/finance/transform/260/w880h180/20180326/zi4T-fysqfnf9377213.png)\n\n## VIP课程推荐\n\n## [新浪直播](http://live.finance.sina.com.cn/)\n\n![@@title@@]()\n\n### @@title@@\n\n## APP专享直播\n\n## 热门推荐\n\n![新浪财经公众号](//n.sinaimg.cn/finance/72219a70/20180103/_thumb_23666.png \"新浪财经公众号\")\n\n24小时滚动播报最新的财经资讯和视频,更多粉丝福利扫描二维码关注(sinafinance)\n\n## [股市直播](http://live.finance.sina.com.cn)\n\n## [7X24小时](http://live.sina.com.cn/zt/f/v/finance/globalnews1)\n\n![杨伟民](https://n.sinaimg.cn/zhuanlan/transform/170/w85h85/20221230/f01f-b20f33f6583ea1f69aefeedf1b86f051.jpg)\n![董明珠还能为格力奋战多久?|《至少一个小时》](//n.sinaimg.cn/finance/transform/525/w300h225/20201030/ecb0-kcaeqzy3436531.jpg)\n![梁建章:中国人口衰竭的速度前所未有](//n.sinaimg.cn/finance/transform/525/w300h225/20201030/75b9-kcaeqzy3438114.jpg)\n![冷友斌回应网友质疑:说价格高 对飞鹤不公平!](//n.sinaimg.cn/finance/transform/525/w300h225/20201030/c485-kcaeqzy3442305.jpg)\n![王中军:我喜欢比较“江湖”一点的人](//n.sinaimg.cn/finance/transform/525/w300h225/20201030/a563-kcaeqzy3444360.jpg)\n\n[新浪财经意见反馈留言板](http://news.sina.com.cn/feedback/post.html)\n\n[新浪简介](http://ir.sina.com.cn/index.shtml)|[广告服务](http://emarketing.sina.com.cn/)|[About Sina](http://corp.sina.com.cn/eng/)\n \n[联系我们](http://www.sina.com.cn/contactus.html)|[招聘信息](http://corp.sina.com.cn/chn/sina_job.html)|[通行证注册](https://login.sina.com.cn/signup/signup.php)\n \n[产品答疑](http://help.sina.com.cn/)|[网站律师](http://www.sina.com.cn/intro/lawfirm.shtml)|[SINA English](http://english.sina.com)\n\nCopyright © 1996-2024 SINA Corporation\n\nAll Rights Reserved 新浪公司 [版权所有](http://www.sina.com.cn/intro/copyright.shtml)" + }, + { + "url": "https://popcj.com/depth/0204832508379321", + "title": "港股医药为何尾盘暴涨?原因找到了 - 古东管家", + "content": "港股医药板块尾盘大幅拉升,恒生生物科技指数收盘涨3.86%,在全市场指数中领先,跟踪该指数的恒生医药ETF(159892)明日或有补涨可能。", + "score": 0.6747586, + "raw_content": "![古东管家](/img/logo.png \"古东管家\")\n![](https://pfoss.lccjapp.cn/webpc/img/notice.279db9f.png)\n![](https://pfoss.lccjapp.cn/webpc/img/app.e13478c.png)\n![](https://pfoss.lccjapp.cn/webpc/img/user.6e90d4b.png)\n\n# 港股医药为何尾盘暴涨?原因找到了\n\n2025-08-05 17:42:24\n\n港股[医药板块](https://www.popcj.com/search/939)尾盘大幅拉升,恒生生物[科技](https://www.popcj.com/search/200)指数收盘涨3.86%,在全市场指数中领先,跟踪该指数的恒生[医药ETF](https://www.popcj.com/search/22757)(159892)明日或有补涨可能。\n\n具体到[个股](https://www.popcj.com/search/5625)来看,[君实生物](https://www.popcj.com/company/68818031)(01877.HK)尾盘飙升,一度近35%。君实生物此前已于6月20日完成配售4100万股新股事项,本次配售的募集资金净额约为10.26亿港元。[公司](https://www.popcj.com/search/1598)拟将配售募集资金净额的70%用于创[新药研发](https://www.popcj.com/search/2541)。\n\n乐普生物大涨超14%,MRG004A启动III期临床,MRG007完成首例患者入组,MRG007近期拿下4700万美元的授权交易(首付款+近期里程碑付款)。\n\n国金[证券](https://www.popcj.com/search/23)此前表示,全球双抗开发热情高涨,国产抗体行业迎来创新成果兑现窗口期。君实生物对单抗开发具有丰富经验,其开发的PD-1单抗分子特瑞普利是最早上市的国产PD-1抑制剂,目前特瑞普利在国内上市众多适应症,同时持续拓展全球商业化网络。\n\n*免责声明:所有平台仅提供服务对接功能,资讯信息、数据资料来源于第三方,其中发布的文章、视频、数据仅代表内容发布者个人的观点,并不代表泡财经平台的观点,不构成任何投资建议,仅供参考,用户需独立做出投资决策,自行承担因信赖或使用第三方信息而导致的任何损失。投资有风险,入市需谨慎。*\n\n![古东管家](https://pfoss.lccjapp.cn/webpc/img/via.f708421.png)\n\n请先登录后发表评论\n\n## 共*0*条评论\n\n扫码在手机上查看\n\n## 相关文章\n\n### 南天信息(000948):中标中国银行股份有限公司广东省分行采购项目,中标金额为167.72万元\n\n![古东管家](https://pfoss.lccjapp.cn/upload/images/thumb_245.png)\n\n### 电气风电(688660):中标重庆广酉新能源有限公司采购项目,中标金额为2.17亿元\n\n![古东管家](https://pfoss.lccjapp.cn/upload/images/thumb_243.png)\n\n### 创新药熄火,医疗板块活跃\n\n![古东管家](https://pfoss.lccjapp.cn/upload/images/thumb_239.png)\n\n[联系我们](/about)**|** 媒体合作 popcj@gudongtech.com**|** 投诉建议 popcj@gudongtech.com\n\n[粤ICP备2020093236号-3](https://beian.miit.gov.cn/)**|** [粤ICP备2020093236号-4](https://beian.miit.gov.cn/)**|** [用户协议](/user/agreement)**|** [隐私政策](/user/privacy)**|** [网站地图](https://www.gudongtech.com/baidu/sitemap.txt)\n\n郑重声明:古东管家所有平台仅提供服务对接功能,其中发布的文章、视频、数据仅代表内容发布者个人的观点,与古东管家平台的立场无关,不对您构成任何投资建议,仅供参考。用户需独立做出投资决策,风险自担。投资有风险,选择需谨慎。\n\n版权所有  ©  深圳市古东管家科技有限责任公司\n\n![古东管家](https://pfoss.lccjapp.cn/webpc/img/qrcode.5d92976.png)\n\n扫码下载古东管家APP" + }, + { + "url": "https://m.lejucaijing.com/news-7355823170131785420.html?&originflag=commonshare", + "title": "创新药持续大涨,恒生医药ETF再创新高,多只个股翻倍 - 乐居财经", + "content": "7月29日早盘,港股医药延续上涨行情,恒生医药ETF(159892)盘中涨超1%,2025年涨幅超86%,在全市场ETF中涨幅靠前;其持仓股三生制药年内涨幅超400%。", + "score": 0.5948579, + "raw_content": "## \n\n![](//res.leju.com/resources/app/touch/newsleju/v1/images/d_login_icon.png)\n\n## 热门搜索\n\n## 搜索历史[清空](javascript:;)\n\n## 创新药持续大涨,恒生医药ETF再创新高,多只个股翻倍\n\n[有连云](https://m.lejucaijing.com/column-771.html) 2025-07-29 12:52 4122阅读\n\n7月29日早盘,港股医药延续上涨行情,恒生医药ETF(159892)盘中涨超1%,2025年涨幅超86%,在全市场ETF中涨幅靠前;其持仓股三生制药年内涨幅超400%,[诺诚健华](https://m.lejucaijing.com/company/74055.html)、乐普生物、[信达生物](https://m.lejucaijing.com/company/74035.html)等年内涨超100%。\n\n消息面来看,国家育儿补贴制度实施方案7月28日公布。从2025年1月1日起,对符合法律法规规定生育的3周岁以下婴幼儿发放补贴,至其年满3周岁。育儿补贴按年发放,现阶段国家基础标准为每孩每年3600元。\n\n7月28日,[恒瑞医药](https://m.lejucaijing.com/company/64873.html)公告与生物制药公司GSK达成协议,将HRS-9821项目的全球独家权利和至多11个项目的全球独家许可的独家选择权有偿许可给GSK。GSK将向恒瑞支付5亿美元的首付款,里程碑付款的潜在总金额约120亿美元。\n\n来源:有连云\n\n![](https://ess.leju.com/common/photo/16-1-LePSkd5gLuz2NRGaJWD4zdglvhyt21xFPMKEM1Al3jN1HRHCZzzhC45nxLMOOMyYAjMRLNkP4dN3QJFp.jpg?s=216X280)\n![](https://ess.leju.com/common/photo/16-1-LePSkd5gLuz2NRGaJWD4zdglvhyt21xFPMKEM1Al3jN1HRHCZzzhC45nxLMOOMyYAjMRLNkP4dN3QJFp.jpg?s=216X280)\n\n《乐居财经精选》\n\n### 民营房企反扑\n\n![](//res.leju.com/assets/app/news/v3/static/news_detail/weekly_icon.png)\n![地产卷王必备!CRIC深度智联强势登场](https://ess.leju.com/common/photo/16-1-B4HYuUplUtegPLpJuvEk2FLXvFrSO6eFE0T1Yc1dKSUlWLSvyfCk46JFbIzw3RvLpQDuDUVhO3eyCkKb.jpg)\n\n#### 相关文章\n\n#### 24小时热门文章\n\n#### 最新文章\n\n## 评论\n\n点击下载App参与更多互动\n\n### 前往乐居财经APP查看原文,体验更佳\n\n## 关注\n\n## 原创\n\n## 数据\n\n## 榜单\n\n![](https://ess.leju.com/common/photo/16-1-ZMYDKwOhsiFzUDBGABVGlB0l2Qp763zLiAX7aLyffQrSXKAM5OkWSpoXFo8lHA9FdIPiiIH0shR2xc1C.jpg)" + }, + { + "url": "https://guba.eastmoney.com/news,sz159892,1544579467.html", + "title": "$恒生医药ETF(SZ159892)$这个ETF一直是折价,什么原因? - 股吧", + "content": "$恒生医药ETF(SZ159892)$ 这个ETF一直是折价,什么原因? 发布于东方财富 ... 24小时点击排行. 美股大涨A股也有利好. 信号来了,支撑反弹水到渠成! 中大力德", + "score": 0.56489134, + "raw_content": null + }, + { + "url": "https://www.stcn.com/article/detail/2347585.html", + "title": "又涨了!创新药ETF还能疯多久? - 证券时报", + "content": "今年以来,涨幅最大的赛道当属港股创新药,相关指数的最大涨幅已超60%。受访者向《国际金融报》记者表示,今日创新药的大涨主要得益于政策面的有力催化。当前", + "score": 0.2578636, + "raw_content": null + } + ], + "response_time": 4.45 + } +} \ No newline at end of file diff --git a/experiencemaker/tool/web_search_cache/test b/experiencemaker/tool/web_search_cache/test new file mode 100644 index 00000000..0eb232f6 --- /dev/null +++ b/experiencemaker/tool/web_search_cache/test @@ -0,0 +1,39 @@ +{ + "results": [ + { + "url": "https://www.stcn.com/article/detail/2347585.html", + "raw_content": "又涨了!创新药ETF还能疯多久?\n===============\n\n首页\n快讯\n要闻\n股市\n新股信披+公司港美股数据基金金融视听评论专题产经创投科创板新三板投教ESG滚动 \n\n公众号 电子报客户端\n\n您当前的位置:证券时报>基金>正文\n又涨了!创新药ETF还能疯多久? \n来源:国际金融报 作者:夏悦超 2025-07-02 07:18\n点赞\n\n分享\n#### 微信扫一扫:分享 \n在多重利好因素的共同推动下,创新药相关ETF(交易型开放式指数基金)再次集体迎来大涨。\n7月1日,多只创新药主题ETF在午后出现放量大涨,收盘时涨幅超2%,在全市场ETF涨幅榜单中位居前列。其中,涨得最高的1只ETF涨幅超5%。\n从成交额来看,有1只港股创新药ETF的成交额达到了59.8亿元,排在全市场股票型ETF之前。\n今年以来,涨幅最大的赛道当属港股创新药,相关指数的最大涨幅已超60%。受访者向《国际金融报》记者表示,今日创新药的大涨主要得益于政策面的有力催化。当前,部分创新药企业的基本面呈现出良好的发展态势,有望在未来实现进一步的提升。然而,投资者也需警惕板块估值泡沫以及个股股价虚高的潜在风险。综合来看,预计板块整体的涨幅在后续可能会相对上半年更温和。\n放量大涨\n今日港股休市,跟踪港股市场的跨境ETF照常交易,尤以港股创新药相关ETF交投最火热。\nWind数据显示,截至7月1日收盘,多只创新药主题ETF领涨全市场ETF。具体来看,同时覆盖A股和港股医药的富国创新药ETF收涨5.88%,排在全市场第一;跟踪港股医药的汇添富港股通创新药ETF收涨4.44%,排在第二。此外,多只创新药ETF和港股通创新药ETF涨幅超2%,占据全市场ETF涨幅榜单的前列。\n\n数据来源:Wind\n从成交额来看,以全市场投资股票资产的ETF为例,广发港股创新药ETF当日成交额达到59.8亿元,排在全市场第一,汇添富港股通创新药ETF、银华港股创新药ETF当日成交额均突破10亿元。\n从ETF当日走势来看,多只创新药ETF的放量大涨主要集中在午后,且这种强劲的反弹表现一直持续到收盘前。从上述成交数据可以发现,由于当日港股休市,而大部分创新药企业又是在港股上市,因此部分看好创新药的资金通过A股大量买入港股通ETF,这其中包括多只港股创新药ETF。在港股休市时,港股通ETF场外申购和赎回业务被暂停,场内不断买入将会炒高溢价,类似的事情也曾发生在港股通在内的跨境ETF上。\n数据显示,截至7月1日,多只港股创新药ETF溢价率超3%,富国创新药ETF溢价率超5%。目前已有富国、汇添富等公募基金公司就相关ETF二级市场价格溢价的风险做出警示,并表示若溢价未有效回落,将采取临时停牌、延长停牌等措施警示风险。\n今年以来,创新药是涨势最强的赛道,相关指数集体走强。截至7月1日,恒生港股通创新药指数年内涨幅超60%,恒生生物科技指数年内涨幅超50%。\n未来行情\n近段时间,有关创新药的动态不断。\n6月30日,恒生港股通创新药指数作出了重大变更:指数将修订编制方案,明确剔除CXO等类型的公司,此类公司主要是医药外包服务类,意味着该指数将真正成为一只“纯度”100%的创新药指数。对此,易方达基金相关人士认为,这将更加精准反映我国创新药企的整体表现,能够更纯粹地聚焦以对外专利授权为主要商业模式的创新药公司,有效表征本轮创新药产业发展趋势。\n7月1日,国家医保局、国家卫生健康委印发《支持创新药高质量发展的若干措施》,其中提及,加大创新药研发支持力度,支持创新药进入基本医保药品目录和商业健康保险创新药品目录,支持创新药临床应用,提高创新药多元支付能力等。\n谈及今日创新药板块大涨的原因,止于至善投资总经理何理向《国际金融报》记者表示,主要受上述文件催化。\n“上述文件从研发端、支付端、销售入院端等多个角度给创新药发展有力的支持。从去年开始,国内就不断出台支持创新药发展的各项政策。预计在国内政策的支持下,创新药企业的研发方向会更有针对性,审批效率和抵达临床实际需求的效率也会提升。同时,医保和商保的联合支持有利于提升创新药的销售空间。”何理表示。\n今年上半年,在创新药板块大涨的背景下,部分主投该板块的基金取得了超85%的惊人涨幅。然而,随着涨幅的不断扩大,一些投资者也开始担忧:创新药行情能否持续,是否存在短期调整的风险。\n对此,融智投资基金经理兼高级研究员包金刚向记者分析称,在一系列鼓励政策的支持下,不少创新药企业实现了快速增长,盈利状况向好,这对估值提供了长期支撑。不过,部分资金可能会忽略创新药企业背后的风险,个别上市公司股价虚高,甚至出现了估值泡沫。\n“今年下半年,随着多款国产创新药有望实现出海授权、国家政策对于发展创新药的明确支持、数个国产重磅创新药物年内获批、创新药销售收入高速增长以及更多生物科技企业迎来商业化盈利,创新药板块的基本面有望进一步提升,板块情绪也有望保持积极。不过,整体板块涨幅可能较上半年更为温和。”包金刚表示。\n何理认为,从基本面来看,创新药是整个医药板块中成长性最强的板块。头部多家药企今年已经实现盈亏平衡或扭亏为盈,预计长期会有比较好的表现。从短期来看,不少管线预期已经充分反映在估值中,后续需要结合估值挑选有充足安全边际的个股。“当前头部几家创新药的估值反应较为充分,后续需要注意临床试验数据不及预期、海外政策限制等风险,目前可以布局一些安全边际还较高的创新药个股”。\n责任编辑: 冉超\n创新药ETF\n港股创新药\n港股通创新药ETF\n声明:证券时报力求信息真实、准确,文章提及内容仅供参考,不构成实质性投资建议,据此操作风险自担\n下载“证券时报”官方APP,或关注官方微信公众号,即可随时了解股市动态,洞察政策信息,把握财富机会。\n网友评论\n登录 后可以发言\n发送\n网友评论仅供其表达个人看法,并不表明证券时报立场\n暂无评论 \n为你推荐\n\n突然爆发!刚刚,狂掀涨停潮! 券商中国 周乐 2025-07-02 15:00 \n突然爆发!A股涨停潮、港股最高涨超130%,发生了什么? 证券时报 吴永芳 2025-07-02 17:29 \n菜鸟全球供应链再升级 亚太仓配网络覆盖10个国家地区 证券时报网 康殷 2025-07-02 14:06 \n达芙妮王俊刚:女鞋行业头部效应凸显 以模式转型与品牌焕新抢占增长先机 证券时报网 曹晨 2025-07-02 14:06 \n罕见!控制24个账户,持股超30%仍不报告!证监局:罚4250万 券商中国 刘艺文 2025-07-02 15:00 \n7月1日股票ETF资金净流出126亿元 中国基金报 天心 2025-07-02 15:14 \n\n时报 热榜\n换一换\n\n稳定币第一股申请银行牌照\n江苏战新基金集群亮相深圳!产业专项基金向头部GP抛出合作橄榄枝\n多家新能源车企,销量创新高!\n揭秘涨停丨迎政策利好,创新药多股涨停\n主力资金丨涨超280%,主力资金大幅出手这只新股!\n【公告精选】长春高新、欣旺达筹划发行H股\n扎克伯格宣布组建“超级智能实验室”,11人“天才团队”首曝光\n7月“金股”出炉!哪些公司最受青睐?赚钱机会在哪儿?\n\n热点 视频\n换一换\n\n国家发展改革委:以旧换新销售额已超1.4万亿元,第三批消费品以旧换新资金将在7月下达 06-26 13:06 \n充电宝召回风波持续发酵!民航局出台充电宝乘机新规:禁止旅客携带无3C标识及被召回的充电宝乘坐境内航班 06-27 14:41 \n耗资24亿的古城,四年累计亏损超10亿!张家界市委书记、市长表态:深刻汲取教训,努力盘活! 06-29 14:22 \n停职七个月后 苗华被免中央军委委员 06-29 14:23 \n深交所发布创业板“轻资产、高研发投入” 认定标准 06-30 17:01 \n\n关于我们|服务条例|联系我们|版权声明|网站地图|线索提交\n备案号:粤ICP备09109218号|增值电信业务经营许可证:粤B2-20080118|互联网新闻信息服务许可证10120170066\n违法和不良信息举报电话:0755-83514034 邮箱:bwb@stcn.com中央网信办违法和不良信息举报中心|证券时报网举报中心\n本网站提供之资料或信息,仅供投资者参考,不构成投资建议。\n深圳证券时报社有限公司版权所有,未经书面授权禁止转载及各种形式的软件开发。\nCopyright © 2008-2025 Shenzhen Securities Times Co., Ltd. All Rights Reserved\n站长统计站长统计", + "images": [ + + ] + }, + { + "url": "https://finance.sina.com.cn/stock/marketresearch/2024-05-09/doc-inaurxyc6090539.shtml", + "raw_content": "为什么说恒生医药进入中期布局拐点?\nã€€ã€€ç‚’è‚¡å°±çœ‹é‡‘éº’éºŸåˆ†æžå¸ˆç ”æŠ¥ï¼Œæƒå¨ï¼Œä¸“ä¸šï¼ŒåŠæ—¶ï¼Œå…¨é¢ï¼ŒåŠ©æ‚¨æŒ–æŽ˜æ½œåŠ›ä¸»é¢˜æœºä¼šï¼\n  来源:券业行家\n  为什么说恒生医药进入中期布局拐点?\n  “五一”前后,中国资产成了全球资金的热门“打卡”板块,港股、中概股领跑全球。\n  转折点在4月19日周五晚间,中国证监会发布5项资本市场对港合作措施,旨在拓展优化沪深港通机制,助力香港巩固提升国际金融中心地位。次周一4月22日起,港股主要指数、美股中概股连续逼空大涨。4月23日,瑞银将中国大陆和中国香港股票评级上调至超配,为近年来外资大行罕见唱多中国资产。\n  5月2日,中资股行情引爆。\nã€€ã€€å—ç›ŠäºŽâ€œäº”ä¸€â€æœŸé—´ä¸­å›½ç»æµŽå¤è‹éªŒè¯çš„ä¹è§‚åˆ¤æ–­ï¼Œå…¨çƒèµ„æœ¬åŠ é€Ÿæµå…¥é¦™æ¸¯å¸‚åœºï¼Œæ¸¯è‚¡ç§‘æŠ€æ¿å—å…¨çº¿æ‹‰å‡ã€‚æ’ç”Ÿç§‘æŠ€æŒ‡æ•°ï¼ˆHSTECH.HI)再度拉涨4.45%,恒生香港上市生物科技指数(HSHKBIO.HI)更是暴涨4.7%,美股中概股指数(US36966)也大涨6.96%。从4月22日到5月6日,恒生指数罕见连续10个交易日收涨,累计上涨14.51%,颇有“技术性牛市”之势。\n\nã€€ã€€é¦™æ¸¯å¸‚åœºæ ¸å¿ƒæŒ‡æ•°è¡¨çŽ°ï¼ˆæ•°æ®æ¥æºï¼šWind)\nã€€ã€€æ’ç”Ÿé¦™æ¸¯ä¸Šå¸‚ç”Ÿç‰©ç§‘æŠ€æŒ‡æ•°æ˜¯æ¸¯è‚¡ç”Ÿç‰©åˆ¶è¯ã€åŸºå› æµ‹åºã€ç»†èƒžæ²»ç–—ç­‰é¢†åŸŸï¼Œé«˜ç§‘æŠ€ã€é«˜æˆé•¿ã€é«˜å¼¹æ€§çš„ä»£è¡¨æŒ‡æ•°ï¼Œæˆä»½è‚¡åŒ…æ‹¬åœ¨é¦™æ¸¯äº¤æ˜“æ‰€é€è¿‡ä¸Šå¸‚è§„åˆ™ç¬¬18Aç« ä¸Šå¸‚çš„å…¬å¸ï¼Œåˆ›æ–°è¯ã€CXOç­‰å‰æ²¿ç§‘æŠ€å«é‡å°¤ä¸ºçªå‡ºã€‚æ­¤é—´å…¶åœ¨æ¸¯è‚¡æ ¸å¿ƒæŒ‡æ•°ä¸­ä¸€æžç‹¬ç§€çš„è¡¨çŽ°ï¼Œè¡¨æ˜Žå…¨çƒèµ„é‡‘åœ¨é£Žé™©åå¥½æ‹”å‡æ—¶å¯¹æ¸¯è‚¡åŒ»è¯ç”Ÿç‰©æ¿å—çš„ç‰¹åˆ«åž‚é’ã€‚\n  这是一个值得高度关注的新动向,港股医药生物板块或迎来中期布局的拐点窗口。\n\n  三年震荡调整或见底\n  复盘看,恒生香港上市生物科技指数于2019å¹´12月16日发布。2021å¹´6月29日摸顶历史最高点2925.74点,到2024å¹´4月19日触底最低点712.69点。期间历经近三年巨幅震荡调整,历史极点的最大调整幅度高达75.64%。\n\n  (数据来源:Wind,时间:2019.12.16~2024.5.8)\nã€€ã€€æ¸¯è‚¡ç”Ÿç‰©ç§‘æŠ€æŒ‡æ•°å‘å¸ƒæ—¶æ­£å€¼æ–°å† ç–«æƒ…çˆ†å‘ï¼Œç”Ÿç‰©åŒ»è¯æ¿å—åœ¨éœ€æ±‚æŽ¨åŠ¨ä¸‹æŒç»­æ‹‰é«˜ä¼°å€¼ï¼ŒæŒ‡æ•°è‡ªå‘å¸ƒæ—¥èµ·ï¼ŒçŸ­çŸ­ä¸€å¹´åŠæ—¶é—´å†…ï¼Œåˆ°2021å¹´6月28日收盘价历史最高日时,足足上涨了96.54%ã€‚åŸºæœ¬é¢ä»·å€¼åœ¨è¾ƒçŸ­æ—¶é—´å†…è¢«æå‰é€æ”¯æ˜¯æŒ‡æ•°è§é¡¶çš„é‡è¦åŽŸå› ã€‚\nã€€ã€€è§é¡¶æ·±è·Œçš„å¤–éƒ¨æŽ¨åŠ¨å› ç´ ï¼Œåˆ™æ˜¯ç¾Žå›½å¯¹ä¸­ç¾Žç§‘æŠ€äº¤æµçš„æ”¿ç­–è½¬å‘ã€‚2021å¹´5月12æ—¥ï¼Œç¾Žå›½å›½ä¼šå‚è®®é™¢å•†åŠ¡å§”å‘˜ä¼šé€šè¿‡â€œæ— å°½å‰æ²¿æ³•æ¡ˆâ€ï¼Œæ‰¹å‡†åœ¨5年内拨款1100å¤šäº¿ç¾Žå…ƒç”¨äºŽæŠ•èµ„äººå·¥æ™ºèƒ½ã€åŠå¯¼ä½“ã€é‡å­è®¡ç®—ã€å…ˆè¿›é€šä¿¡ã€ç”Ÿç‰©æŠ€æœ¯å’Œå…ˆè¿›èƒ½æºç­‰å…³é”®æŠ€æœ¯é¢†åŸŸçš„åŸºç¡€å’Œé«˜çº§ç ”ç©¶ã€å•†ä¸šåŒ–ä»¥åŠæ•™è‚²å’ŒåŸ¹è®­è®¡åˆ’ï¼Œä»¥åº”å¯¹æ¥è‡ªä¸­å›½æ—¥ç›Šä¸¥é‡çš„ç«žäº‰åŽ‹åŠ›ã€‚å¾ˆå¿«ï¼Œ6月8日,美参议院通过升级版的《2021美国创新和竞争法案》,投资金额提升到2500亿美元。\n  自高点向下,生物科技指数持续单边下挫近一年时间才开启震荡整理。2024å¹´1月,美国再提《生物安全法案》,中国创新药、CXO企业再遭冲击,生物科技指数开始逐步触底。\n  Wind数据显示,截至2024å¹´5月8日,恒生香港上市生物科技指数在连续反弹之后,市盈率TTM也仅为28.64倍,处于历史分位点2.59%的极低位置,远低于历史中位值的90.9倍、最大值的2126.3倍;市净率1.72倍,处于历史分位点3.93%;市销率0.85倍,处于历史分位点3.49%,修复空间巨大,当前估值性价比、安全边际均较高。\n\n  创新药获全方位支持\nã€€ã€€åœ¨å¸‚åœºæƒ…ç»ªè½¬æš–æ—¶ï¼ŒåŒ»è¯ç”Ÿç‰©æ–¹å‘å—åˆ°èµ„é‡‘åå¥½çš„å¤§é€»è¾‘æ˜¯ï¼Œè€é¾„åŒ–åŠ é€ŸèƒŒæ™¯ä¸‹ï¼ŒåŒ»ç–—å¼€æ”¯åœ¨éœ€æ±‚ç«¯æ˜¯è¾ƒä¸ºåˆšæ€§çš„ã€‚4月下旬,针对沪深港市场生物科技、医药板块行情,上海证券分析,需求端和供给端6å¤§å› ç´ å°†å…±åŒæŽ¨åŠ¨æ¶ˆè´¹åŒ»ç–—å¸‚åœºå¢žé•¿ã€‚\n  需求端:(1)政策红利释放。“健康中国2030”健全全民健康制度体系,推进以“治病为中心”向“以健康为中心”转变。(2ï¼‰æ”¯ä»˜èƒ½åŠ›æå‡ã€‚å¯¹æ ‡å‘è¾¾å›½å®¶ç»éªŒï¼Œä¸­å›½å¥åº·äº§ä¸šæ”¯ä»˜èƒ½åŠ›ä»æœ‰è¾ƒå¤§æå‡ç©ºé—´ã€‚ï¼ˆ3ï¼‰äººå£ç»“æž„å˜åŒ–ã€‚éšç€è€é¾„åŒ–åŠ é€Ÿï¼Œå…·æœ‰æ›´å¼ºç»æµŽå®žåŠ›ä¸Žæ•™è‚²æ°´å¹³çš„â€œ60后”步入退休潮,医疗健康迎来旺盛需求。(4ï¼‰åŽç–«æƒ…æ—¶ä»£æ›´åŠ å…³æ³¨å¥åº·ã€‚å¤§ä¼—æ—¥å¸¸ä¿å¥æ„è¯†æå‡ï¼Œè®©ä¿å…»ç–—æ„ˆæˆä¸ºå¯æŒç»­çš„æ—¥å¸¸ã€‚\n  供给端:(1ï¼‰äº§å“è½¬å‘åˆ›æ–°é©±åŠ¨ï¼ŒåŠŸæ•ˆæå‡ä¸”åŠ é€Ÿè¿­ä»£ï¼Œæœç€å¤šå…ƒåŒ–å‘å±•ï¼Œæ»¡è¶³æ¶ˆè´¹è€…å¤šæ ·åŒ–éœ€æ±‚ã€‚2023年保健食品备案3632款,同比增长52%。(2)数字化医疗生态布局日臻完善,有效链接健康消费群体,包括信息科普、远程问诊、线上购药、疾病管理。其中信息科普通过各类社交平台、搜索引擎等提供保健养生、疾病诊治等相关信息,渗透率大于60%。\n  近来,在外部压力下,创新药行业逐步酝酿全方位支持政策。今年3月“创新药”首次写入国务院政府工作报告,具体支持政策正渐次推出。4月7日,北京市医疗保障局等9部门制定《北京市支持创新医药高质量发展若干措施(2024)(征求意见稿)》。4月11日,国家医保局发布《2023年医疗保障事业发展统计快报》,通过建立覆盖申报、评审、测算、谈判等全流程的创新药支持机制,支持更多新药好药纳入医保,2023,年目录调整中,有,57,个药品实现了“当年获批、当年纳入目录”。\nã€€ã€€åˆ›æ–°è¯ä½œä¸ºæ–°è´¨ç”Ÿäº§åŠ›ä»£è¡¨ï¼Œâ€œæ”¿ç­–åŒ…â€æŽ¥ç»­å‡ºå°ï¼ŒåŒ»è¯äº§ä¸šè¿Žæ¥æ·±åˆ»å˜é©ï¼Œå¸‚åœºå¯¹åŒ»è¯æ¿å—ã€åˆ›æ–°è¯äº§ä¸šçš„æ‚²è§‚é¢„æœŸä¹Ÿæ­£åœ¨æ ¹æœ¬æ‰­è½¬ã€‚\n  事实上,2022å¹´~2023年全球药企管线规模排名前25名中有3,家中国企业上榜,表明我国在全球药物创新中崭露头角。“十四五”期间,我国创新药正积极实现高质量“引进来”和高水平“走出去”。2021å¹´~2022,年,中国创新药授权引进(license-in)交易项目多涉及CD分子、RNAè¯ç‰©ç­‰èµ›é“ã€‚åŒæ—¶ï¼Œç™¾æµŽç¥žå·žçš„æ³½å¸ƒæ›¿å°¼å®žçŽ°äº†æœ¬åœŸæ–°è¯å‡ºæµ·â€œé›¶çš„çªç ´â€ï¼Œä¸”å¯¹å¤–æŽˆæƒï¼ˆlicense-out)也捷报频频。据医药魔方数据统计,2022,年,我国license-out交易总金额达277亿美元,较2020年增长122.08%ã€‚ï¼ˆæ¥æºï¼šã€Šä¸­å›½åŒ»è¯äº§ä¸šé«˜è´¨é‡å‘å±•çŠ¶å†µè°ƒç ”ï¼ˆ2021—2023ï¼‰ã€‹ç ”ç©¶æŠ¥å‘Šç»¼è¿°ï¼Œä¸­å›½é£Ÿå“è¯å“ç›‘ç®¡æ‚å¿—2024年第三期)\n  国金证券认为,全球创新药是科技进步兑现的重要领域,中国创新药产业链正在崛起;院内诊疗全面恢复,国家对创新药的鼓励支持政策陆续出台,以及中国药企国际化步伐的推进,行业整体将迎来业绩与市场表现的同步向上。中银证券表示,医药生物行业伴随“医药反腐”影响消退和基数压力消化,后续业绩增速有望逐季改善,国内政策支持及创新药出海前景均值得期待。\n\n  美降息周期利好科技\nã€€ã€€ä»Žå¤–éƒ¨çŽ¯å¢ƒè€Œè¨€ï¼Œè¿‘æœŸæ¸¯è‚¡æŒç»­ä¸Šæ¶¨ï¼Œæˆ–å—ç¾Žå…ƒé™æ¯å‘¨æœŸæ¥ä¸´ã€æ—¥æœ¬ç»§ç»­åŠ æ¯æ¦‚çŽ‡é™ä½Žç­‰å½±å“ï¼Œå¤–èµ„éƒ¨åˆ†æµå‘æ¸¯è‚¡å¸‚åœºã€‚\n  在港股5月投资策略中,国信证券认为,港股有望孕育长期基本面修复行情。随着恒生科技多年来估值底部的确认,恒生指数、恒生港股通、恒生高股息、恒生科技的估值底部全部确认完毕,将打消一直以来“杀估值”的最大不确定性,有助于长线资金回流这些公司并开启长期基本面修复。\nã€€ã€€å›½æ³°å›å®‰ç ”æŠ¥è¡¨ç¤ºï¼Œæ¸¯è‚¡åœ¨è¿‘æœŸå¤§å¹…èµ°é«˜åŽï¼Œä¼šå‡ºçŽ°ä¸€å®šåˆ†æ­§å’Œéœ‡è¡ï¼Œä½†éšç€ä¸­å›½åœ¨ç»æµŽã€æ”¿ç­–é¢†åŸŸä¸ç¡®å®šæ€§é™ä½Žï¼Œæ¸¯è‚¡ä»æœ‰å¯ä¸ºç©ºé—´ã€‚å›½å†…æ–¹é¢ï¼Œç»æµŽæ”¿ç­–ä»¥ç¨³ä¸ºä¸»ï¼Œåœ°äº§æ”¿ç­–æŒç»­ä¼˜åŒ–ï¼Œæ¸¯è‚¡å¸‚åœºç›ˆåˆ©é¢„æœŸæ”¹å–„ï¼Œå¤–èµ„ä¸Šä¿®å›½å†…ç»æµŽå‰æ™¯ï¼Œæ¸¯è‚¡å¸‚åœºæƒ…ç»ªä¿®å¤ã€‚æµ·å¤–æ–¹é¢ï¼Œç¾Žè”å‚¨å‘µæŠ¤é‡‘èžå’ŒåŠ³åŠ¨åŠ›å¸‚åœºçš„æ„å›¾æ˜Žæ˜¾ï¼Œå¸‚åœºæµåŠ¨æ€§è¿›ä¸€æ­¥æ”¶ç´§çš„é£Žé™©ä¸‹é™ã€‚\n  而医药生物、创新药、CXO产业方向,除了关注基本面趋势,作为科技主线,估值还受到全球资金流动,尤其美元周期的深刻影响。近期,虽然美联储降息预期有所延后,但美元降息周期开启的判断并未改变,对科技创新主线的估值形成有利的全球流动性边际改善环境。\nã€€ã€€åˆ›æ–°ä¸»çº¿æ˜¯ç©¿è¶Šå‘¨æœŸçš„æ³•å®ã€‚ç”Ÿç‰©ç§‘æŠ€æ˜¯åŒ»è¯è¡Œä¸šåˆ›æ–°åº¦é«˜ã€æˆé•¿ç©ºé—´å¤§çš„ä¸»æµèµ›é“ã€‚æ’ç”Ÿé¦™æ¸¯ä¸Šå¸‚ç”Ÿç‰©ç§‘æŠ€æŒ‡æ•°å·²åŸºæœ¬å®Œæˆæ”¿ç­–åº•ã€åŸºæœ¬é¢åº•ã€ä¼°å€¼åº•ã€èµ„é‡‘é¢åº•çš„å…±æŒ¯ï¼Œé€‚åˆè¿›è¡Œä¸­é•¿å‘¨æœŸå¸ƒå±€æŒæœ‰ï¼Œåˆ†äº«åŒ»è¯ç”Ÿç‰©é«˜æˆé•¿æ ¸å¿ƒèµ„äº§çš„å¤åˆ©å¢žé•¿ã€‚\nã€€ã€€è¿‘æœŸä»½é¢æŒç»­å¢žé•¿ã€å‡€å€¼æ˜Žæ˜¾ä¿®å¤ã€åœºå†…ä»·æ ¼æŒç»­åå¼¹çš„æ’ç”ŸåŒ»è¯ETF(159892.SZ),正是跟踪恒生香港上市生物科技指数的高光品种,在跟踪同指数的ETF产品中,恒生医药ETF的基金规模、成交额最大,流动性良好且支持T+0交易。同时,恒生医药ETF也有对应的场外联接基金(A类:016970;C类:016971),费率在同类中属最低档水平,没有股票账户的场外投资者也可以关注。\nã€€ã€€å€¼å¾—ä¸€æçš„æ˜¯ï¼Œæ¸¯è‚¡åŒ»è¯ç”Ÿç‰©æ¿å—å› ä¸Šå¸‚åˆ¶åº¦å·®å¼‚ï¼ŒåŒ…å«äº†ä¸€äº›æœªèƒ½åœ¨A股上市的优质稀缺的生物医药企业,恒生香港上市生物科技指数前十大成分股中绝大部分都没有在A股上市。这也是港股医药生物板块区别Aè‚¡çš„æ ¸å¿ƒèµ„äº§ä¼˜åŠ¿ï¼Œè¯¥æŒ‡æ•°å‰åå¤§æˆåˆ†è‚¡æ¶µç›–å„ä¸ªç»†åˆ†é¢†åŸŸçš„é¾™å¤´ä¼ä¸šï¼Œè¦†ç›–ç”Ÿç‰©åŒ»è¯äº§ä¸šé“¾ä¸Šä¸­ä¸‹æ¸¸å„ä¸ªç»†åˆ†çŽ¯èŠ‚ã€‚\n  总之,通过恒生医药ETF一揽子买入了在港上市的一流新老实力药企,在周期拐点布局或可拥有较强的穿越周期能力。\n\n责任编辑:杨红卜\n\nVIP课程推荐\n新浪直播\n\n@@title@@\nAPP专享直播\n热门推荐\n\n24å°æ—¶æ»šåŠ¨æ’­æŠ¥æœ€æ–°çš„è´¢ç»èµ„è®¯å’Œè§†é¢‘ï¼Œæ›´å¤šç²‰ä¸ç¦åˆ©æ‰«æäºŒç»´ç å…³æ³¨ï¼ˆsinafinance)\n股市直播\n7X24小时\n\n新浪财经意见反馈留言板\n新浪简介|广告服务|About Sina\n联系我们|招聘信息|通行证注册\n产品答疑|网站律师|SINA English\nCopyright © 1996-2024 SINA Corporation\nAll Rights Reserved 新浪公司 版权所有", + "images": [ + + ] + }, + { + "url": "https://m.lejucaijing.com/news-7355823170131785420.html?&originflag=commonshare", + "raw_content": "热门搜索\n搜索历史清空\n创新药持续大涨,恒生医药ETF再创新高,多只个股翻倍\n有连云 2025-07-29 12:52 4249阅读\n7月29日早盘,港股医药延续上涨行情,恒生医药ETF(159892)盘中涨超1%,2025年涨幅超86%,在全市场ETF中涨幅靠前;其持仓股三生制药年内涨幅超400%,诺诚健华、乐普生物、信达生物等年内涨超100%。\n消息面来看,国家育儿补贴制度实施方案7月28日公布。从2025年1月1日起,对符合法律法规规定生育的3周岁以下婴幼儿发放补贴,至其年满3周岁。育儿补贴按年发放,现阶段国家基础标准为每孩每年3600元。\n7月28日,恒瑞医药公告与生物制药公司GSK达成协议,将HRS-9821项目的全球独家权利和至多11个项目的全球独家许可的独家选择权有偿许可给GSK。GSK将向恒瑞支付5亿美元的首付款,里程碑付款的潜在总金额约120亿美元。\n来源:有连云\n\n《乐居财经精选》\n民营房企反扑\n\n相关文章\n24小时热门文章\n最新文章\n评论\n点击下载App参与更多互动\n前往乐居财经APP查看原文,体验更佳\n关注\n原创\n数据\n榜单", + "images": [ + + ] + }, + { + "url": "https://popcj.com/depth/0204832508379321", + "raw_content": "港股医药为何尾盘暴涨?原因找到了\n2025-08-05 17:42:24\n港股医药板块尾盘大幅拉升,恒生生物科技指数收盘涨3.86%,在全市场指数中领先,跟踪该指数的恒生医药ETF(159892)明日或有补涨可能。\n具体到个股来看,君实生物(01877.HK)尾盘飙升,一度近35%。君实生物此前已于6月20日完成配售4100万股新股事项,本次配售的募集资金净额约为10.26亿港元。公司拟将配售募集资金净额的70%用于创新药研发。\n乐普生物大涨超14%,MRG004A启动III期临床,MRG007完成首例患者入组,MRG007近期拿下4700万美元的授权交易(首付款+近期里程碑付款)。\n国金证券此前表示,全球双抗开发热情高涨,国产抗体行业迎来创新成果兑现窗口期。君实生物对单抗开发具有丰富经验,其开发的PD-1单抗分子特瑞普利是最早上市的国产PD-1抑制剂,目前特瑞普利在国内上市众多适应症,同时持续拓展全球商业化网络。\n免责声明:所有平台仅提供服务对接功能,资讯信息、数据资料来源于第三方,其中发布的文章、视频、数据仅代表内容发布者个人的观点,并不代表泡财经平台的观点,不构成任何投资建议,仅供参考,用户需独立做出投资决策,自行承担因信赖或使用第三方信息而导致的任何损失。投资有风险,入市需谨慎。\n\n请先登录后发表评论\n共0条评论\n扫码在手机上查看\n相关文章\n诚志股份(000990):上半年归母净利润同比降低89.78%\n\n阳光电源(300274):中标晋能控股晋北能源(山西)有限公司采购项目,中标金额为4.06亿元\n\n苏豪弘业(600128):中标上海市长宁区妇幼保健院采购项目,中标金额为1438.74万元\n\n联系我们| 媒体合作 popcj@gudongtech.com| 投诉建议 popcj@gudongtech.com\n粤ICP备2020093236号-3| 粤ICP备2020093236号-4| 用户协议| 隐私政策| 网站地图\n郑重声明:古东管家所有平台仅提供服务对接功能,其中发布的文章、视频、数据仅代表内容发布者个人的观点,与古东管家平台的立场无关,不对您构成任何投资建议,仅供参考。用户需独立做出投资决策,风险自担。投资有风险,选择需谨慎。\n版权所有 © 深圳市古东管家科技有限责任公司\n\n扫码下载古东管家APP", + "images": [ + + ] + } + ], + "failed_results": [ + { + "url": "https://guba.eastmoney.com/news,sz159892,1544579467.html", + "error": "Failed to fetch content" + } + ], + "response_time": 3.72 +} \ No newline at end of file diff --git a/test/config.json b/test/config.json new file mode 100644 index 00000000..d1fb68f8 --- /dev/null +++ b/test/config.json @@ -0,0 +1,6 @@ +{ + "stock_code": "002027", + "min_pages": 1, + "download_dir": "reports_pdf", + "years_ago": 5 +} \ No newline at end of file diff --git a/test/test1.py b/test/test1.py new file mode 100644 index 00000000..685d6d16 --- /dev/null +++ b/test/test1.py @@ -0,0 +1,11 @@ +# 2025年半年报点评:Q2业绩同比增长,CPU、DCU业务进展顺利 +# https://data.eastmoney.com/report/info/AP202508061722561937.html +# +# https://pdf.dfcfw.com/pdf/H3_AP202508061722561937_1.pdf + +import requests +headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" +} +url = requests.get("https://data.eastmoney.com/report/stock.jshtml", headers=headers) +print(url.text) \ No newline at end of file diff --git a/test/test2.py b/test/test2.py new file mode 100644 index 00000000..9de166d9 --- /dev/null +++ b/test/test2.py @@ -0,0 +1,391 @@ +import random + +import requests +import json +import re +import os +from urllib.parse import urljoin +from time import sleep +import pycurl +from io import BytesIO +from PyPDF2 import PdfReader +from datetime import datetime, timedelta + +# 全局配置 +BASE_URL = "https://reportapi.eastmoney.com/report/list" +DETAIL_BASE_URL = "https://data.eastmoney.com/report/info/" + +# 读取config.json获取stock_code +with open('config.json', 'r', encoding='utf-8') as f: + config = json.load(f) +STOCK_CODE = config.get('stock_code', '600519') +MIN_PAGES = config.get('min_pages', 20) +DOWNLOAD_DIR = config.get('download_dir', "reports_pdf") +YEARS_AGO = config.get('years_ago', 2) +os.makedirs(DOWNLOAD_DIR, exist_ok=True) + +# 随机User-Agent列表 +USER_AGENTS = [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0" +] + + +def get_random_user_agent(): + """获取随机User-Agent""" + import random + return random.choice(USER_AGENTS) + + +def fetch_jsonp_data(page_no=1): + """ + 获取研究报告列表数据 + :param page_no: 页码 + :return: 解析后的数据字典 + """ + # 计算日期 + today = datetime.today() + end_time = today.strftime('%Y-%m-%d') + begin_time = (today - timedelta(days=365 * YEARS_AGO)).strftime('%Y-%m-%d') + + # 检查是否存在已保存的原始数据 + raw_data_dir = "raw_data" + raw_data_file = os.path.join(raw_data_dir, f"page_{page_no}_{STOCK_CODE}_{begin_time}_{end_time}.json") + + if os.path.exists(raw_data_file): + print(f"使用已保存的原始数据: {raw_data_file}") + try: + with open(raw_data_file, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + print(f"读取已保存数据失败: {e}") + + params = { + "cb": "datatable6333112", + "pageNo": page_no, + "pageSize": 50, + "code": STOCK_CODE, + "industryCode": "*", + "industry": "*", + "rating": "*", + "ratingchange": "*", + "beginTime": begin_time, + "endTime": end_time, + "fields": "", + "qType": 0, + "p": page_no, + "pageNum": page_no, + "pageNumber": page_no, + "_": int(time.time() * 1000) # 使用当前时间戳 + } + headers = { + "User-Agent": get_random_user_agent(), + "Referer": "https://data.eastmoney.com/" + } + try: + response = requests.get(BASE_URL, params=params, headers=headers) + response.raise_for_status() + # 提取JSON部分 + json_str = re.search(r'\((.*)\)', response.text).group(1) + data = json.loads(json_str) + + # 保存原始数据到本地 + if not os.path.exists(raw_data_dir): + os.makedirs(raw_data_dir, exist_ok=True) + + with open(raw_data_file, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + print(f"原始数据已保存: {raw_data_file}") + return data + except Exception as e: + print(f"获取第{page_no}页数据失败: {e}") + return None + + +def get_report_detail(info_code): + """ + 获取研究报告详情页内容 + :param info_code: 报告ID + :return: 详情页HTML内容 + """ + # 检查是否存在已保存的详情页HTML + detail_data_dir = "detail_data" + detail_html_file = os.path.join(detail_data_dir, f"detail_{info_code}.html") + + if os.path.exists(detail_html_file): + print(f"使用已保存的详情页HTML: {detail_html_file}") + try: + with open(detail_html_file, 'r', encoding='utf-8') as f: + return f.read() + except Exception as e: + print(f"读取已保存详情页失败: {e}") + + url = urljoin(DETAIL_BASE_URL, f"{info_code}.html") + headers = { + "User-Agent": get_random_user_agent(), + "Referer": "https://data.eastmoney.com/" + } + + try: + response = requests.get(url, headers=headers) + response.raise_for_status() + + # 保存详情页HTML原始数据 + if not os.path.exists(detail_data_dir): + os.makedirs(detail_data_dir, exist_ok=True) + + with open(detail_html_file, 'w', encoding='utf-8') as f: + f.write(response.text) + + print(f"详情页HTML已保存: {detail_html_file}") + return response.text + except Exception as e: + print(f"获取报告详情{info_code}失败: {e}") + return None + + +def parse_detail_page(html, info_code): + """ + 解析详情页获取PDF下载链接及相关信息 + :param html: 详情页HTML + :param info_code: 报告ID + :return: dict,包含PDF下载URL及命名所需字段 + """ + try: + # 使用正则提取zwinfo变量 + match = re.search(r'var zwinfo\s*=\s*({.*?});', html, re.DOTALL) + if not match: + return None + zwinfo = json.loads(match.group(1)) + + # 保存解析后的zwinfo数据 + detail_data_dir = "detail_data" + zwinfo_file = os.path.join(detail_data_dir, f"zwinfo_{info_code}.json") + with open(zwinfo_file, 'w', encoding='utf-8') as f: + json.dump(zwinfo, f, ensure_ascii=False, indent=2) + + print(f"zwinfo数据已保存: {zwinfo_file}") + + # 提取所需字段 + return { + 'attach_url': zwinfo.get('attach_url'), + 'notice_title': zwinfo.get('notice_title', ''), + 'short_name': zwinfo.get('short_name', ''), + 'notice_date': zwinfo.get('notice_date', ''), + 'source_sample_name': zwinfo.get('source_sample_name', ''), + 'attach_pages': zwinfo.get('attach_pages', '') + } + except Exception as e: + print(f"解析详情页失败: {e}") + return None + + +def is_pdf_complete(pdf_path, expected_pages): + """ + 检查PDF页数是否与预期一致 + :param pdf_path: PDF文件路径 + :param expected_pages: 预期页数(int) + :return: bool + """ + try: + with open(pdf_path, 'rb') as f: + reader = PdfReader(f) + actual_pages = len(reader.pages) + return actual_pages == expected_pages, actual_pages + except Exception as e: + print(f"读取PDF页数失败: {e}") + return False, 0 + + +def download_pdf(pdf_url, filename): + """ + 使用pycurl下载PDF文件(模拟curl请求) + + 参数: + pdf_url (str): PDF文件的URL + filename (str): 保存文件名(不含路径) + + 返回: + bool: 是否下载成功 + """ + save_path = os.path.join(DOWNLOAD_DIR, filename) + buffer = BytesIO() + c = pycurl.Curl() + + try: + # 设置curl选项 + c.setopt(pycurl.URL, pdf_url) + c.setopt(pycurl.WRITEDATA, buffer) + c.setopt(pycurl.FOLLOWLOCATION, True) + c.setopt(pycurl.MAXREDIRS, 5) + c.setopt(pycurl.CONNECTTIMEOUT, 30) + c.setopt(pycurl.TIMEOUT, 300) + + # 设置防爬虫headers + headers = [ + f"User-Agent: {get_random_user_agent()}", + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Referer: https://data.eastmoney.com/", + "Accept-Language: zh-CN,zh;q=0.9" + ] + c.setopt(pycurl.HTTPHEADER, headers) + + # 执行下载 + c.perform() + + # 验证响应 + if c.getinfo(pycurl.HTTP_CODE) != 200: + print(f"下载失败 HTTP {c.getinfo(pycurl.HTTP_CODE)}") + return False + + # 保存文件 + with open(save_path, 'wb') as f: + f.write(buffer.getvalue()) + + print(f"✓ 成功下载 {filename}") + return True + + except pycurl.error as e: + errno, errstr = e.args + print(f"pycurl错误({errno}): {errstr}") + return False + except Exception as e: + print(f"下载异常: {str(e)}") + return False + finally: + c.close() + buffer.close() + + +def process_all_reports(): + """处理所有研究报告""" + # 获取第一页数据 + first_page_data = fetch_jsonp_data(1) + if not first_page_data: + return + + total_page = first_page_data.get("TotalPage", 1) + total_reports = first_page_data.get("hits", 0) + print(f"共发现{total_reports}篇研究报告,{total_page}页") + + # 处理所有页面 + for page in range(1, total_page + 1): + print(f"\n正在处理第{page}/{total_page}页...") + # 获取当前页数据 + if page == 1: + page_data = first_page_data + else: + page_data = fetch_jsonp_data(page) + if not page_data: + continue + # 处理每篇报告 + report_list = page_data.get("data", []) + random.shuffle(report_list) + for report in report_list: + info_code = report.get("infoCode") + if not info_code: + continue + + # 检查页数,只有大于20页的才下载 + attach_pages = report.get("attachPages", 0) + try: + attach_pages = int(attach_pages) + except (ValueError, TypeError): + attach_pages = 0 + + if attach_pages < MIN_PAGES: + print(f"跳过页数不足的报告: {report.get('title')} (页数: {attach_pages})") + continue + + print(f"\n处理报告: {report.get('title')} [{info_code}] (页数: {attach_pages})") + # 获取详情页 + detail_html = get_report_detail(info_code) + if not detail_html: + continue + # 解析PDF链接及命名信息 + detail_info = parse_detail_page(detail_html, info_code) + if not detail_info or not detail_info.get('attach_url'): + print("未找到PDF链接") + continue + # 组装文件名,避免重复拼接 + notice_title = detail_info.get('notice_title', '').strip().replace('/', '_') + short_name = detail_info.get('short_name', '').strip().replace('/', '_') + notice_date = detail_info.get('notice_date', '').replace('-', '')[:8] # 只取年月日 + source_sample_name = detail_info.get('source_sample_name', '').strip().replace('/', '_') + + filename_parts = [] + filename_parts.append(notice_date) + # 判断source_sample_name是否已在notice_title中 + if source_sample_name and source_sample_name not in notice_title: + filename_parts.append(source_sample_name) + # 判断short_name是否已在notice_title中 + if short_name and short_name not in notice_title: + filename_parts.append(short_name) + filename_parts.append(notice_title) + # 分离文件名和目录 + pdf_filename = f"{'_'.join(filename_parts)}.pdf" + pdf_subdir = f"{short_name}" + + # 判断是否为深度报告(页数大于20页) + if attach_pages >= 20: + pdf_subdir = f"{short_name}/深度报告" + + pdf_full_path = os.path.join(DOWNLOAD_DIR, pdf_subdir, pdf_filename) + + # 检查并创建目录 + pdf_dir = os.path.join(DOWNLOAD_DIR, pdf_subdir) + if not os.path.exists(pdf_dir): + os.makedirs(pdf_dir, exist_ok=True) + print(f"创建目录: {pdf_dir}") + + # 检查文件是否已存在 + if os.path.exists(pdf_full_path): + print(f"文件已存在,跳过下载: {pdf_full_path}") + continue + + # 下载PDF并校验页数,最多重试3次 + max_retries = 5 + for attempt in range(1, max_retries + 1): + download_pdf(detail_info['attach_url'], os.path.join(pdf_subdir, pdf_filename)) + # 校验PDF页数 + try: + expected_pages = int(detail_info.get('attach_pages', 0)) + except Exception: + expected_pages = 0 + is_complete = True + actual_pages = 0 + if expected_pages > 0: + is_complete, actual_pages = is_pdf_complete(pdf_full_path, expected_pages) + if is_complete: + print(f"✓ PDF页数校验通过:{actual_pages}页") + break + else: + print( + f"✗ PDF页数不符:实际{actual_pages}页,预期{expected_pages}页,正在重试({attempt}/{max_retries})...") + # 删除不完整文件 + try: + os.remove(pdf_full_path) + except Exception: + pass + sleep(1) + else: + break + sleep(60 * attempt) + + else: + print(f"!!! PDF多次下载后仍不完整:{pdf_full_path}") + # 礼貌性延迟 + sleep(30) + + +if __name__ == "__main__": + import time + + start_time = time.time() + + process_all_reports() + + end_time = time.time() + print(f"\n全部完成,耗时: {end_time - start_time:.2f}秒") \ No newline at end of file diff --git a/test/test3.py b/test/test3.py new file mode 100644 index 00000000..c6ca4c3e --- /dev/null +++ b/test/test3.py @@ -0,0 +1,97 @@ +import random + +import requests +import json +import re +import os +from urllib.parse import urljoin +from time import sleep +import pycurl +from io import BytesIO +from PyPDF2 import PdfReader +from datetime import datetime, timedelta + +DOWNLOAD_DIR = "./" + +# 随机User-Agent列表 +USER_AGENTS = [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0" +] + + +def get_random_user_agent(): + """获取随机User-Agent""" + import random + return random.choice(USER_AGENTS) + + +def download_pdf(pdf_url, filename): + """ + 使用pycurl下载PDF文件(模拟curl请求) + + 参数: + pdf_url (str): PDF文件的URL + filename (str): 保存文件名(不含路径) + + 返回: + bool: 是否下载成功 + """ + save_path = os.path.join(DOWNLOAD_DIR, filename) + buffer = BytesIO() + c = pycurl.Curl() + + try: + # 设置curl选项 + c.setopt(pycurl.URL, pdf_url) + c.setopt(pycurl.WRITEDATA, buffer) + c.setopt(pycurl.FOLLOWLOCATION, True) + c.setopt(pycurl.MAXREDIRS, 5) + c.setopt(pycurl.CONNECTTIMEOUT, 30) + c.setopt(pycurl.TIMEOUT, 300) + + # 设置防爬虫headers + headers = [ + f"User-Agent: {get_random_user_agent()}", + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Referer: https://data.eastmoney.com/", + "Accept-Language: zh-CN,zh;q=0.9" + ] + c.setopt(pycurl.HTTPHEADER, headers) + + # 执行下载 + c.perform() + + # 验证响应 + if c.getinfo(pycurl.HTTP_CODE) != 200: + print(f"下载失败 HTTP {c.getinfo(pycurl.HTTP_CODE)}") + return False + + # 保存文件 + with open(save_path, 'wb') as f: + f.write(buffer.getvalue()) + + print(f"✓ 成功下载 {filename}") + return True + + except pycurl.error as e: + errno, errstr = e.args + print(f"pycurl错误({errno}): {errstr}") + return False + except Exception as e: + print(f"下载异常: {str(e)}") + return False + finally: + c.close() + buffer.close() + +if __name__ == '__main__': + url_list = [ + "https://pdf.dfcfw.com/pdf/H3_AP202508061722531920_1.pdf?1754495126000.pdf", + ] + + url_list = [x.split("?")[0] for x in url_list] + for url in url_list: + name = url.split("_")[1] + download_pdf(url, f"{name}.pdf") diff --git a/test/test4.py b/test/test4.py new file mode 100644 index 00000000..5045fba0 --- /dev/null +++ b/test/test4.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +def analyze_corrupted_text(text): + """分析乱码文本的字节构成""" + print(f"分析文本: {text}") + print(f"文本长度: {len(text)}") + + # 显示每个字符的Unicode码点 + print("字符分析:") + for i, char in enumerate(text[:20]): # 只显示前20个字符 + print(f" {i}: '{char}' -> U+{ord(char):04X}") + + # 尝试不同的编码方式 + print("\n编码尝试:") + + try: + # 方法1: Latin1 -> UTF-8 + bytes_latin1 = text.encode('latin1') + result_utf8 = bytes_latin1.decode('utf-8') + print(f"Latin1->UTF-8: {result_utf8}") + except Exception as e: + print(f"Latin1->UTF-8 失败: {e}") + + try: + # 方法2: Latin1 -> GBK + bytes_latin1 = text.encode('latin1') + result_gbk = bytes_latin1.decode('gbk') + print(f"Latin1->GBK: {result_gbk}") + except Exception as e: + print(f"Latin1->GBK 失败: {e}") + + try: + # 方法3: CP1252 -> UTF-8 + bytes_cp1252 = text.encode('cp1252') + result_utf8 = bytes_cp1252.decode('utf-8') + print(f"CP1252->UTF-8: {result_utf8}") + except Exception as e: + print(f"CP1252->UTF-8 失败: {e}") + + # 显示原始字节 + try: + raw_bytes = text.encode('latin1') + print(f"\n原始字节 (Latin1): {raw_bytes}") + print(f"字节十六进制: {raw_bytes.hex()}") + except Exception as e: + print(f"获取原始字节失败: {e}") + + +def main(): + """调试主函数""" + test_texts = [ + "为ä»ä¹è¯´æçå»è¯è¿å¥ä¸­æå¸å±æç¹ï¼", + "åçäºâäºä¸âæé´ä¸­å½ç»æµå¤è¯éªçä¹è§å¤æ­", + "æçç§ææ°ï¼HSTECH.HIï¼åº¦æ¼æ¶4.45%" + ] + + for i, text in enumerate(test_texts, 1): + print(f"\n{'=' * 60}") + print(f"测试 {i}") + print('=' * 60) + analyze_corrupted_text(text) + + +if __name__ == "__main__": + main()