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

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

ただいまの
回答率

91.24%

  • Python

    4246questions

    Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

  • Python 3.x

    2797questions

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

  • MacOS(OSX)

    1421questions

    MacOSとは、Appleの開発していたGUI(グラフィカルユーザーインターフェース)を採用したオペレーションシステム(OS)です。Macintoshと共に、市場に出てGUIの普及に大きく貢献しました。

  • Scrapy

    35questions

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

Scrapyを用いて複数ページから複数要素を1レコードずつ抽出したい

受付中

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 100

fuyutsuki

score 1

Scrapyを用いてクローリング&スクレイピングを行なっており、
なんとなく欲しい情報を取得できたものの思った通りに並んで
くれず、原因や解決方法がわからない状態です。

<やりたいこと>
ページAに複数ページ(仮にページBとCとDとします)のリンクが貼られており、
更にページBとCとDからリンク先のページがあるとします。

↓こんな感じです。(伝わりますでしょうか・・・)

階層1 階層2 階層3
ページA ページB ページB'
ページC ページC'
ページD

ページAから地名を取得し、
次に、ページBやCで、宿泊施設の名前を取得し、
更に、ページB'やページDで、宿泊施設のURLを取得したいです。

※ページBには複数の宿泊施設の名前があり、全て取得したく、ページB'には1つしかURLは存在しないです。
※ページBとCはページAから、ページC'とDはページCからそれぞれリンクが貼られています。

【表1】

列1 列2 列3
熱海 ホテル熱海 http://www.hotel_atami.jp/
長野 ホテル長野 http://www.hotel_nagano.jp/
長野 民宿長野 http://www.minsyuku_nagano.jp/

というようにCSV形式で取得したいのですが、実際には

【表2】

列1 列2 列3
熱海 ホテル熱海
長野 ホテル長野,民宿長野
http://www.minsyuku_nagano.jp/
http://www.hotel_nagano.jp/
http://www.minsyuku_nagano.jp/

のように空欄ができたりしてしまいます。
どうしたら表1のように取得ができますでしょうか?
また、なぜ現在はこのような取得結果になっているのでしょうか?

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from project1.items import Project1Item

class Project1Spider(CrawlSpider):
    name = 'project1'
    allowed_domains = ['syukuhaku.jp']
    start_urls = ('http://syukuhaku.jp/',)

    rules = (
        Rule(LinkExtractor(allow=r'/cgi-bin/\w+'), callback='parse_topics', follow=True),
        Rule(LinkExtractor(allow=r'/dettail/\w+'), callback='parse_topics_detail'),
    )


    def parse_topics(self, response):
        item = Project1Item()
        item['area'] = response.css('#main > h1 > span:nth-child(1)::text').extract()
        item['name'] = response.css('#main > h1 > span:nth-child(3)::text').extract()
        yield item


    def parse_topics_detail(self, response):
        item = Project1Item()
        item['url'] = response.css('#base-info > table > tr > td > a::attr("href")').extract_first()
        yield item
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

0

scrapy使ったことないのですが、コードと結果(表2)を見る限り、
以下のコードを、タイトル取得(parse_topics)とURL取得(parse_topics_detail)の内側で呼んでいるため、
それぞれの実行時に新しいitemが生まれてしまっており、同じitemに書き込めていないのだと思います。

item = Project1Item()

"Rule"の扱いを知らないのでなんですが、
可能であれば、parse以前に、1つitemを作って、そのitemのarea, name, urlに対してparse結果を書き込むような処理にすれば良いのではないでしょうか。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/12/15 22:36

    naosk8さん
    ご回答ありがとうございます。
    そうか!と思い、以下2種類試してみたのですがうまくいかず・・・
    他に確認すべきところはありますでしょうか?

    1.
    def parse_topics_detail(self, response):
    の中の
    item = Project1Item()
    のみコメントアウトして実行。
    →urlは抽出されない。(CSVにそもそもurl列が存在しない)
     name列には複数の宿泊施設名が入ったまま。


    2.
    def parse_topics(self, response):
    def parse_topics_detail(self, response):
    の2つの中にある
    item = Project1Item()
    をいずれもコメントアウトし、
    start_urls = ('http://syukuhaku.jp/',)
    の下に
    item = Project1Item()
    と記載。
    →何も抽出されない。
     logには NameError: name 'item' is not defined と出ている。

    キャンセル

  • 2017/12/16 04:06 編集

    実装を進めるにあたって、スコープやクラス/インスタンスの概念を確認した方がスムーズかもしれません。
    メソッド内で初期化したもの/定義したものは、基本的にはそのメソッド内でのみ使えます。

    ドキュメントを少し見た限り、
    http://scrapy-ja.readthedocs.io/ja/latest/topics/spiders.html
    Ruleのcallbackは、ItemかRequestを返す必要があるようで、
    つまりは1ページへのスクレイピングは、基本的には少なくとも1つの抽出データ、
    もしくは新たなスクレイピング先へのリダイレクトを返す必要があるということかと思います。

    手元で試していませんが、自分なら以下の2通りを試します。
    1. Ruleとしては、parse_topicsを探す処理のみを記載し、parse_topic内から関連するURLを探しに行く。

    ```
    rules = (
    Rule(LinkExtractor(allow=r'/cgi-bin/\w+'), callback='parse_topics', follow=True),
    )

    item = None # <- 以下のself.itemはこれを参照する
    def parse_topics(self, response):
    self.item = Item()
    self.item['area'] = response.css('#main > h1 > span:nth-child(1)::text').extract()
    self.item['name'] = response.css('#main > h1 > span:nth-child(3)::text').extract()
    detail_url = response.(何らか詳細ページへのリンクを取得する).extract()
    yield scrapy.Request(url, callback=self.parse_topics_detail)

    def parse_topics_detail(self, response):
    self.item['url'] = response.css('#base-info > table > tr > td > a::attr("href")').extract_first()
    # ここで初めてItemを書き出す
    yield item
    ```

    2. 当初のRuleを保ちつつ、parse_topicsとparse_topics_detailを紐づけるキーを
     何らかページ内から抽出して、抽出後、それぞれのリスト(旅館名データと旅館のURLデータ)を突合する。

    ```
    def parse_topics(self, response):
    ....
    item['key'] = 何かページ内から取得できるItemを一意に特定するための値。
    yield item

    def parse_topics_detail(self, response):
    ....
    item['key'] = 何かページ内から取得できるItemを一意に特定するための値。
    yield item

    ```

    コメント欄はMarkdownで書けないのですね、少し見にくいですが悪しからず。。

    キャンセル

  • 2017/12/18 23:11

    ご返信ありがとうございます。
    いくつか試していたため遅くなってしまいました。
    ドキュメントを見ても何が書いてあるかよくわからないレベルでしてお手数をおかけしすみません。

    記載いただいた1の方法を試してみたのですが、

    NameError: name 'item' is not defined

    と出てしまい何も抽出されなくなり、うまくいきませんでした・・・
    ちなみにitemはitem.pyという別ファイルで定義しており、

    import scrapy
    class Project1Item(scrapy.Item):
    area = scrapy.Field()
    name = scrapy.Field()
    surl = scrapy.Field()

    というように記述しています。
    そのため、parse_topicsメソッド内の
    self.item = Item()

    self.item = Project1Item()
    のように対応する名前にしてみたのですが同様の結果でした。

    ご指摘の通り、基本的な理解が乏しいので改めてクラスなどの概念を確認した上で
    また挑んでみたいと思います。

    キャンセル

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

ただいまの回答率

91.24%

関連した質問

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

  • Python

    4246questions

    Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

  • Python 3.x

    2797questions

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

  • MacOS(OSX)

    1421questions

    MacOSとは、Appleの開発していたGUI(グラフィカルユーザーインターフェース)を採用したオペレーションシステム(OS)です。Macintoshと共に、市場に出てGUIの普及に大きく貢献しました。

  • Scrapy

    35questions

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