0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

yfinanceのget_newsが空・CURRENTLY DOWNになる原因と対処【2026年11月】

0
Posted at

3 行まとめ

  • 2026年10月中旬から、yfinance の Ticker.news / get_news() は全銘柄で空リストを返すようになった(yfinance の Issue による)。11月3日ごろからは YFDataException: *** YAHOO! FINANCE IS CURRENTLY DOWN! *** を投げる。Yahoo Finance が落ちているわけではなく、ニュース取得に使っていた /xhr/ncp エンドポイントが廃止されたのが原因
  • 11月3日リリースの yfinance 1.8.0 で、取得先が株価ページと同じ GraphQL に切り替わった。日本株(7203.T など)でも手元で5件ずつ取れた。ただし取れるのは英語ニュースだけで、ロケールを日本に変えると0件になる
  • 自作の投資分析ツールでは、残っているログの1か月分すべてで Claude にニュースが1件も渡っていなかった。例外を握りつぶして 0/5 銘柄 を INFO ログに流すだけの作りだったため、気づけなかった

何が起きたか:ログに「0/5 銘柄」が並んでいた

自作の投資分析ツール(RakuScan)は、平日の16時にスクリーニングを回し、上位5銘柄のニュース見出しを yfinance で取ってから Claude に統合分析させている。ニュース取得の部分はこうなっている。

# scripts/daily_run.py(抜粋)
top_symbols = [r["symbol"] for r in top_results]
try:
    from src.data.news_fetcher import fetch_news
    news_by_symbol = fetch_news(top_symbols)
    enriched = []
    for r in top_results:
        entry = dict(r)
        news = news_by_symbol.get(r["symbol"], [])
        if news:
            entry["recent_news"] = [n["title"] for n in news]
        enriched.append(entry)
    logger.info(
        "ニュース取得完了: %d/%d 銘柄",
        sum(1 for s in top_symbols if s in news_by_symbol),
        len(top_symbols),
    )
except Exception as e:
    logger.warning("ニュース取得失敗(ニュースなしで続行): %s", e)
    enriched = top_results

ニュースが取れなくても分析は止めない設計にしている。ニュースは補助情報なので、この判断自体は間違っていないと思う。

ところが記事のネタ探しでログを見直したら、こうなっていた。

2026-10-05 16:14:21 [INFO] __main__: ニュース取得完了: 0/5 銘柄
2026-10-06 16:15:55 [INFO] __main__: ニュース取得完了: 0/5 銘柄
...
2026-10-30 16:15:06 [INFO] __main__: ニュース取得完了: 0/5 銘柄
2026-11-02 16:13:42 [INFO] __main__: ニュース取得完了: 0/5 銘柄
2026-11-03 16:14:56 [WARNING] src.data.news_fetcher: ニュース取得エラー (symbol=xxxx): *** YAHOO! FINANCE IS CURRENTLY DOWN! ***
2026-11-03 16:14:56 [INFO] __main__: ニュース取得完了: 0/5 銘柄

残っているログは10月5日以降の分だけだが、その範囲では 23回の実行すべてが 0/5 だった(11月5日はスクリーニング自体がスキップされて 0/0)。しかも10月と11月で壊れ方が違う。

期間 挙動 ログ
10月(残存ログの範囲) 例外なし・空リスト INFO の 0/5 銘柄 だけ
11月3日〜 YFDataException 銘柄ごとに WARNING+0/5 銘柄

10月は例外もエラーログも出ていない。INFO レベルに 0/5 と出るだけなので、ログを眺めても異常に見えなかった。

原因:/xhr/ncp が404を返すようになった

RakuScan が使っている yfinance 1.2.0 の get_news() はこうなっている。

# yfinance 1.2.0 base.py(抜粋)
url = f"{_ROOT_URL_}/xhr/ncp?queryRef={query_ref}&serviceKey=ncp_fin"
payload = {
    "serviceConfig": {
        "snippetCount": count,
        "s": [self.ticker]
    }
}

data = self._data.post(url, body=payload)
if data is None or "Will be right back" in data.text:
    raise YFDataException("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***")
try:
    data = data.json()
except _json.JSONDecodeError:
    ...
news = data.get("data", {}).get("tickerStream", {}).get("stream", [])

Yahoo Finance 内部の /xhr/ncp に POST し、返ってきた JSON の data.tickerStream.stream をニュースとして返す。このエンドポイントを直接叩いてみた。

import yfinance as yf
from yfinance.const import _ROOT_URL_

t = yf.Ticker("7203.T")
for tk in ["7203.T", "AAPL"]:
    r = t._data.post(
        f"{_ROOT_URL_}/xhr/ncp?queryRef=latestNews&serviceKey=ncp_fin",
        body={"serviceConfig": {"snippetCount": 10, "s": [tk]}},
    )
    print(tk, r.status_code, r.headers.get("content-type"))

print(len(t.history(period="5d")))
7203.T 404 text/html
AAPL 404 text/html
5

日本株も米国株も404になる。一方で history() は普通に5行返るので、Yahoo Finance 全体が落ちているわけではない。例外メッセージの「CURRENTLY DOWN」は実態と違う。

yfinance 本体にも、10月19日付けで同じ報告が上がっていた。

Issue によると、経緯はこうだ。

  • 報告者の環境では10月16日まで動いていて、遅くとも10月19日から壊れた
  • 最初は404の本文が {"message":"Not Found"} という 正しい JSON だった。そのため data.tickerStream.stream が見つからず、例外もログも出さずに [] を返していた
  • 11月3日の追記によると、404の本文が HTML(<title>Yahoo</title>、本文に "Will be right back" を含む)に変わった。これで "Will be right back" in data.text の分岐に入り、CURRENTLY DOWN 例外が投げられるようになった
  • 株価ページのニュース一覧は、別のエンドポイントからクライアント側で読み込む方式に変わったようだ、と報告されていた

RakuScan のログでも、11月3日から例外に変わっている。この切り替わりは Issue の追記と日付まで一致する。

一方で、RakuScan は Issue が「まだ動いていた」とする10月5日〜16日の時点で、すでに0件だった。それより前のログは残っていないので、日本株で /xhr/ncp からニュースが取れていた時期があったのかは確認できない。少なくとも言えるのは「残っているログの範囲では一度も取れておらず、10月19日以降は確実に今回の廃止で取れない状態だった」ということだ。

対処1:yfinance 1.8.0 に上げる

Issue は、11月3日(UTC)の yfinance 1.8.0 リリースと同時に close された。

https://github.com/ranaroussi/yfinance/releases/tag/1.8.0
1.8.0 の get_news() は中身が丸ごと書き換わっている。

# yfinance 1.8.0 base.py(抜粋)
lang, region = YfConfig.locale.lang, YfConfig.locale.region

# Cache per request: a different count, tab or locale is a different result
cache_key = (count, list_name, lang, region)
if cache_key in self._news:
    return self._news[cache_key]

# Same GraphQL request the quote news page makes since Yahoo retired /xhr/ncp
slug = f"list=finance-{region}-{lang}-ticker-{list_name}"

コメントにあるとおり、Yahoo が /xhr/ncp を廃止したことを受けて、株価ページ自身が使っている GraphQL リクエストに乗り換えている。キャッシュのキーにロケールが入っていることからも分かるとおり、ニュースはロケール単位で取得する 方式になった。

RakuScan の環境を汚さないよう、使い捨ての venv に 1.8.0 を入れて試した。

uv venv /tmp/yf18/.venv
uv pip install --python /tmp/yf18/.venv/bin/python 'yfinance==1.8.0'
import yfinance as yf

for s in ["7203.T", "9984.T", "AAPL"]:
    news = yf.Ticker(s).get_news(count=5)
    print(s, len(news))
    for a in news[:2]:
        c = a.get("content", {})
        print("   ", c.get("pubDate"), "|", (c.get("title") or "")[:70])
7203.T 5
    2026-11-02T21:28:56Z | Earnings live updates: Airbnb stock jumps as Wall Street cheers 'growt...
    2026-11-05T19:19:27Z | Toyota Motor (TM) Q2 2027 Earnings Call Transcript
9984.T 5
    2026-11-05T19:34:20Z | SoftBank's $5.4 billion AI bet hits a stunning roadblock
    2026-11-05T14:55:02Z | Stocks to Watch: Qualcomm, Zillow, Toyota, Airbnb
AAPL 5
    2026-11-06T00:19:40Z | Dow Jones Futures Fall; Nasdaq Dives 5.5%, Nvidia, Sandisk, CoreWeave, ...
    2026-11-05T23:00:00Z | Apple, Alphabet, and Meta: 3 Big Takeaways from Recent Earnings

日本株でも取れるようになった。ただし、使う前に知っておきたい点が3つあった。

取れるのは英語ニュースだけ

1.8.0 の docstring には「Yahoo only has news for some locales; for others an empty list is returned.」とある。日本語ニュースが欲しくてロケールを日本に変えてみた。

yf.config.locale.lang = "ja-JP"
yf.config.locale.region = "JP"
print(len(yf.Ticker("7203.T").get_news(count=5)))  # 0

少なくとも ja-JP / JP の組み合わせでは0件で、しかもエラーにならず黙って空リストが返った。日本語ニュースが存在しないのか、slug の形式が合っていないだけなのかまでは切り分けていない。日本株でも、実用上は US ロケールの英語ニュースを使うことになる。

銘柄と関係の薄い記事が混ざる

トヨタ(7203.T)の先頭は「Airbnb の決算ライブ」だった。ソフトバンクグループ(9984.T)の2件目は、タイトルにソフトバンクが出てこない「Stocks to Watch: Qualcomm, Zillow, Toyota, Airbnb」だった。本文のどこかで触れてはいるのだろうが、その銘柄を主題にした記事ではない。見出しを LLM に渡すなら、関連の薄いノイズが混ざる前提で考えたほうがいい。

並び順は厳密な新着順ではなかった

docstring には「newest first」とあり、リクエスト側にも # aboutnessWeight 0 ranks purely by recency というコメントがある。関連度は見ずに新しさだけで並べる設定で、関係の薄い記事が混ざるのもこのためだろう。ところが 7203.T では、11月2日の記事が11月5日の記事より前に来ていた。日付で使うなら、pubDate で並べ直したほうが安全だ。

レスポンス形式は「新形式」

1.8.0 が返すのは {"id": ..., "content": {...}} の形で、content の中に title / pubDate / canonicalUrl が入る。RakuScan の news_fetcher.py は、最初から新旧どちらの形式も読めるようにしてあったので、パーサは書き換えずに済む。

# src/data/news_fetcher.py(抜粋)
def _extract_news_item(item: dict) -> dict | None:
    # 新形式: {"content": {"title": ..., "pubDate": ..., "canonicalUrl": ...}}
    content = item.get("content", {})
    if content:
        title = content.get("title", "")
        pub_date = content.get("pubDate", "")
        url = (
            content.get("canonicalUrl", {}).get("url", "")
            or content.get("clickThroughUrl", {}).get("url", "")
        )
    else:
        # 旧形式: {"title": ..., "link": ..., "providerPublishTime": ...}
        title = item.get("title", "")
        ...

対処2:1.8.0 に上げられないなら yf.Search

Issue のコメントでは、ニュース自体は yf.Search(...).news で取れると報告されていた。1.2.0 のまま試してみた。

for q in ["7203.T", "9984.T", "Toyota"]:
    news = yf.Search(q, news_count=5).news
    print(q, len(news))
7203.T 0
9984.T 0
Toyota 5

ティッカー(7203.T)で検索すると0件だった。英語の社名(Toyota)なら5件返る。しかもこちらは 旧形式(title / link / providerPublishTime がトップレベル)で返ってくる。旧形式も _extract_news_item で読めるのでパーサはそのまま使えるが、日本株で使うなら銘柄コードから英語社名への対応表が必要になる。暫定策としては使えるが、素直に 1.8.0 に上げるほうが楽だ。

なお RakuScan は、この記事を書いている時点ではまだ 1.2.0 のままだ。1.2.0 から 1.8.0 まではマイナーバージョンが6つ離れていて、ニュース以外の取得系にも変更が入っている。上げる前に、以前書いた yfinance の落とし穴で挙げた株価・財務データの取得を一通り確認するつもりでいる。

1か月気づかなかった理由

不具合そのものは yfinance と Yahoo の都合だが、1か月気づかなかったのは設計側の問題だ。

  1. 例外を銘柄単位で握りつぶしている。fetch_news() は銘柄ごとに except Exception で WARNING を出して次へ進む。10月は例外すら出ていなかったので、WARNING も出ていない
  2. 「0件」を INFO で流している。ニュース取得完了: 0/5 銘柄 は、たまたまその日の上位銘柄にニュースがなかった場合とまったく同じ見た目になる
  3. 下流が空でも普通に動く。Claude は recent_news フィールドがなくても、テクニカルや財務の結果だけでもっともらしいレポートを書く。出力を見ても、ニュースが欠けていることは分からない

「補助データなので失敗しても止めない」と「失敗に気づける」は両立できる。止めずに、連続して失敗していることだけを通知すればいい。たとえば、こんなガードを足すのが現実的だと考えている。

# 案:N営業日連続で全銘柄0件なら通知する
hit = sum(1 for s in top_symbols if s in news_by_symbol)
if not top_symbols:
    pass                                    # 対象0銘柄の日(0/0)は判定しない
elif hit == 0:
    streak = news_zero_streak.increment()   # DB等に連続回数を保存
    if streak >= 3:
        _notify_error(
            "daily_run: ニュースが3回連続で0件",
            "yfinance のニュース取得系が壊れている可能性あり",
        )
else:
    news_zero_streak.reset()

1日だけの0件は普通にありうるので、連続回数で判定する。スクリーニングがスキップされて対象が0銘柄の日は、カウントを進めもリセットもしない。非公式 API に頼る補助データは、こうした「黙って0件」がいちばん見つけにくい。前述の yfinance の落とし穴の記事でも、ブロック時に例外ではなく空の DataFrame が返る問題を書いた。今回のニュースも同じ種類の壊れ方だった。

まとめ

  • get_news() が空、または YAHOO! FINANCE IS CURRENTLY DOWN! になるのは、Yahoo が落ちているからではない。/xhr/ncp が廃止されたのが原因で、history() などほかの取得系は動いている
  • yfinance 1.8.0(2026-11-03)に上げれば直る。ただし取れるのは英語ニュースだけで、無関係な記事も混ざり、並び順も厳密な新着順ではない
  • 上げられない場合は yf.Search で取れる。ただし日本株はティッカーではヒットせず、英語社名での検索が必要になる
  • 1.8.0 も、"Will be right back" が本文に含まれるかどうかで障害を判定する作りは変わっていない。次に Yahoo 側が変わったときも、同じ誤報が出る可能性がある
  • 非公式 API の補助データは「黙って0件」で壊れる。止めない設計にするなら、連続0件を検知して通知する仕組みをセットで入れておきたい

この記事は Zenn にも同じ内容を投稿しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?