質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.34%

  • Python 3.x

    7335questions

    Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

  • Scrapy

    104questions

    Scrapyは、Pythonで開発されたオープンソースソフトウェアです。スクレイピングという、Webサービスから必要な情報を取り出したり自動操作をしたりする技術を使うものです。

[Yahooニュースのリスト] PythonのScrapyで二つ目のcallbackを呼ぶ方法

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 269

LIATARET

score 9

 前提・実現したいこと

Yahooニュースの主要リストタブの1件目の情報を取得しようとしています。
※主要リストタブの1件目が動画ありのものだと失敗しますが、対象は動画なしのものです。

取得したいのは下記の5つ、
①タイトル 「title」、
②カテゴリ 「category」、
③タイトルのURL 「url」、
④③のURL先の(リンク付きの)見出し文 「summary」、
⑤④のリンク先の『○○/○○(曜日)○○:○○配信』という文字列 「haisintext」
です。

$ scrapy crawl ynews -o news.json 1> one.txt 2> two.txt

①〜④は問題なくファイルに出力されているのですが、⑤には「日時配信」の文字列ではなく、
その文字列があるページのURLが出力されます。
'haisinlink': '〜'ではなく、'haisintext': '4/29(日) 18:02配信'としたいです。

{'category': 'スポーツ',
'haisinlink': 'https://headlines.yahoo.co.jp/hl?a=20180429-00000132-dal-base',
'summary': '巨人が逆転で2年ぶり7連勝、代打・阿部の今季初安打で同点「最高です!」も今季初',
'title': 'G7連勝 阿部「やっと打てた」',
'url': 'https://news.yahoo.co.jp/pickup/6280827'}

parseメソッドからgetArticleメソッドへのcallback(summary取得)は問題ないのですが、
getArticleメソッドからgetTextメソッドへのcallback(haisintext取得希望)のやり方が分かりません。
どのように記述すれば良いのでしょうか。ご教示お願いいたします。

 settings.py

ROBOTSTXT_OBEY = True
DOWNLOAD_DELAY = 3
HTTPCACHE_ENABLED = False
FEED_EXPORT_ENCODING = 'utf-8'

 発生している問題・エラーメッセージ

2018-04-29 20:48:15 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: nobel_winners)
2018-04-29 20:48:15 [scrapy.utils.log] INFO: Versions: lxml 4.1.1.0, libxml2 2.9.7, cssselect 1.0.3, parsel 1.4.0, w3lib 1.19.0, Twisted 17.9.0, Python 3.6.4 (default, Feb 10 2018, 20:17:51) - [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)], pyOpenSSL 17.5.0 (OpenSSL 1.1.0h  27 Mar 2018), cryptography 2.2.2, Platform Darwin-16.7.0-x86_64-i386-64bit
2018-04-29 20:48:15 [scrapy.crawler] INFO: Overridden settings: {'BOT_NAME': 'nobel_winners', 'DEPTH_LIMIT': 5, 'DOWNLOAD_DELAY': 3, 'FEED_EXPORT_ENCODING': 'utf-8', 'FEED_FORMAT': 'json', 'FEED_URI': 'news.json', 'NEWSPIDER_MODULE': 'nobel_winners.spiders', 'ROBOTSTXT_OBEY': True, 'SPIDER_MODULES': ['nobel_winners.spiders']}
2018-04-29 20:48:15 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
 'scrapy.extensions.telnet.TelnetConsole',
 'scrapy.extensions.memusage.MemoryUsage',
 'scrapy.extensions.feedexport.FeedExporter',
 'scrapy.extensions.logstats.LogStats']
2018-04-29 20:48:15 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
2018-04-29 20:48:15 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
 'scrapy.spidermiddlewares.referer.RefererMiddleware',
 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
 'scrapy.spidermiddlewares.depth.DepthMiddleware']
2018-04-29 20:48:15 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2018-04-29 20:48:15 [scrapy.core.engine] INFO: Spider opened
2018-04-29 20:48:15 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2018-04-29 20:48:15 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6024
2018-04-29 20:48:15 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://news.yahoo.co.jp/robots.txt> (referer: None)
2018-04-29 20:48:19 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://news.yahoo.co.jp/list> (referer: None)
2018-04-29 20:48:23 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://news.yahoo.co.jp/pickup/6280827> (referer: https://news.yahoo.co.jp/list)
2018-04-29 20:48:23 [scrapy.core.scraper] DEBUG: Scraped from <200 https://news.yahoo.co.jp/pickup/6280827>
{'category': 'スポーツ',
 'haisinlink': 'https://headlines.yahoo.co.jp/hl?a=20180429-00000132-dal-base',
 'summary': '巨人が逆転で2年ぶり7連勝、代打・阿部の今季初安打で同点「最高です!」も今季初',
 'title': 'G7連勝 阿部「やっと打てた」',
 'url': 'https://news.yahoo.co.jp/pickup/6280827'}
2018-04-29 20:48:23 [scrapy.core.engine] INFO: Closing spider (finished)
2018-04-29 20:48:23 [scrapy.extensions.feedexport] INFO: Stored json feed (1 items) in: news.json
2018-04-29 20:48:23 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 759,
 'downloader/request_count': 3,
 'downloader/request_method_count/GET': 3,
 'downloader/response_bytes': 22206,
 'downloader/response_count': 3,
 'downloader/response_status_count/200': 3,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2018, 4, 29, 11, 48, 23, 584196),
 'item_scraped_count': 1,
 'log_count/DEBUG': 5,
 'log_count/INFO': 8,
 'memusage/max': 53182464,
 'memusage/startup': 53182464,
 'request_depth_max': 1,
 'response_received_count': 3,
 'scheduler/dequeued': 2,
 'scheduler/dequeued/memory': 2,
 'scheduler/enqueued': 2,
 'scheduler/enqueued/memory': 2,
 'start_time': datetime.datetime(2018, 4, 29, 11, 48, 15, 328173)}
2018-04-29 20:48:23 [scrapy.core.engine] INFO: Spider closed (finished)

 ファイル出力結果 (news.json)

[
{"title": "G7連勝 阿部「やっと打てた」", "category": "スポーツ", "url": "https://news.yahoo.co.jp/pickup/6280827", "summary": "巨人が逆転で2年ぶり7連勝、代打・阿部の今季初安打で同点「最高です!」も今季初", "haisinlink": "https://headlines.yahoo.co.jp/hl?a=20180429-00000132-dal-base"}
]

 該当のソースコード

import scrapy

class NewsItem(scrapy.Item):
    title = scrapy.Field()
    category = scrapy.Field()
    url = scrapy.Field()
    summary = scrapy.Field()
    haisintext = scrapy.Field()
    haisinlink = scrapy.Field()

class YnewsSpider(scrapy.Spider):
    name = "ynews"
    start_urls = ['https://news.yahoo.co.jp/list']

    def parse(self, response):
        for selectElements in response.xpath('//*[@id="main"]/div[1]/div[1]/div[4]/ul'):
            item = NewsItem()    # 先頭の記事が動画ありだと失敗するので、下3行分の.//li[1]を[2]とかにずらしてやり過ごす
            item['title'] = selectElements.xpath('.//li[1]/a/dl/dt/text()').extract_first()
            item['category'] = selectElements.xpath('.//li[1]/a/dl/dd/text()').extract_first()
            item['url'] = selectElements.xpath('.//li[1]/a/@href').extract_first()
            request = scrapy.Request(item['url'], callback = self.getArticle, dont_filter = True)
            request.meta['item'] = item
            yield request

    def getArticle(self, response):
        item = response.meta['item']
        item['summary'] = response.xpath('//*[@id="link"]/text()').extract_first()
        item['haisinlink'] = response.xpath('//*[@id="link"]/@href').extract_first()
        request = scrapy.Request(item['haisinlink'], callback = self.getText, dont_filter = True)
        yield item

    def getText(self, response):
        item = response.meta['item']
        item['haisintext'] = response.xpath('//*[@id="ym_newsarticle"]/div[1]/div/p[1]/text()').extract_first()
        yield item

 『○○/○○(曜日)○○:○○配信』別の方法で取得

しましたが、結局二つ目callback呼び出しは解決出来ないまま...

import scrapy
import urllib, lxml

class NewsItem(scrapy.Item):
    title = scrapy.Field()
    category = scrapy.Field()
    url = scrapy.Field()
    summary = scrapy.Field()
    haisintext = scrapy.Field()
#    haisinlink = scrapy.Field()

class YnewsSpider(scrapy.Spider):
    name = "ynews"
    start_urls = ['https://news.yahoo.co.jp/list']

    def parse(self, response):
        for selectElements in response.xpath('//*[@id="main"]/div[1]/div[1]/div[4]/ul'):
            item = NewsItem()     # 先頭の記事が動画ありだと失敗するので、下3行分の.//li[1]を[2]とかにずらしてやり過ごす
            item['title'] = selectElements.xpath('.//li[3]/a/dl/dt/text()').extract_first()
            item['category'] = selectElements.xpath('.//li[3]/a/dl/dd/text()').extract_first()
            item['url'] = selectElements.xpath('.//li[3]/a/@href').extract_first()
            request = scrapy.Request(item['url'], callback = self.getArticle, dont_filter = True)
            request.meta['item'] = item
            yield request

    def getArticle(self, response):
        item = response.meta['item']
        item['summary'] = response.xpath('//*[@id="link"]/text()').extract_first()
        haisinlink = response.xpath('//*[@id="link"]/@href').extract_first()
        target_url = haisinlink
        res = urllib.request.urlopen(target_url)
        dom = lxml.html.fromstring(res.read())
        haisin_txt = dom.xpath('//*[@id="ym_newsarticle"]/div[1]/div/p[1]/text()')
        result = haisin_txt[0].strip()
        item['haisintext'] = result
        yield item

 補足情報(FW/ツールのバージョンなど)

Python 3.6.4
scrapy 1.5.0

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

{'category': 'スポーツ',
 'haisintext': '5/3(木) 9:34 掲載',
 'summary': '白星無し3連敗…ダルビッシュに地元ファンからブーイング',
 'title': 'ダル3敗目 地元でブーイング',
 'url': 'https://news.yahoo.co.jp/pickup/6281189'}

このようなものを取得するのが目的でしょうか。
でしたら、haisintextにあたるもののxpathが

//*[@id="main"]/div[@class="mainBox"]/div[@class="topicsDetail"]/div[@class="topicsHead"]/div[@class="topicsName"/span[@class="date"]


となっていますので、getArticle 中でhaisintext取得を行えばよいかと思います。例えば下記のように

item['haisintext'] = response.xpath('//div[@class="topicsName"]/span[@class="date"]/text()').extract_first()

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/03 15:15

    回答ありがとうございます。
    分かり難くて申し訳ありません。目的の文字列を取得するページの場所ですが、頂いた回答の例で挙げますと、
    ’url’: ‘http://news.yahoo.co.jp/pickup/6281189’ のページ内の
    ’summary’: ‘白星〜(略)〜ブーイング’ をクリックしたリンク先のページ内にある、
    xpathが『//*[@id=“ym_newsarticle”]/div[1]/div/p[1]/text()』の
    『○○/○○(曜日)○○:○○配信』という文字列になります。
    こちらを getArticle()内のcallback=self.getText()を使って取得する(getTextにデータを渡す)方法が質問の趣旨となります。

    キャンセル

  • 2018/05/10 10:30

    それでしたら

    ```
    def getArticle(self, response):
    item = response.meta['item']
    item['summary'] = response.xpath('//*[@id="link"]/text()').extract_first()
    item['haisinlink'] = response.xpath('//*[@id="link"]/@href').extract_first()
    request = scrapy.Request(item['haisinlink'], callback = self.getText, dont_filter = True)
    yield item
    ```

    ここの最後を

    ```
    request = scrapy.Request(item['haisinlink'], callback = self.getText, dont_filter = True)
    request.meta["item"]=item
    yield request
    ```

    とするのではダメなんでしょうか

    キャンセル

  • 2018/05/12 00:03

    ご指摘の箇所を変更し実行したところ取得出来ました。いまいちyieldが分からず、ググっても解決方法がなかったので諦めておりました。ありがとうございます。助かりました。

    キャンセル

同じタグがついた質問を見る

  • Python 3.x

    7335questions

    Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

  • Scrapy

    104questions

    Scrapyは、Pythonで開発されたオープンソースソフトウェアです。スクレイピングという、Webサービスから必要な情報を取り出したり自動操作をしたりする技術を使うものです。