Sucre1108
@Sucre1108

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Pythonでのスクレイピングにおいて分からないところが、、

URLとタイトルをfile内に保存するにはどうしたら良いですか?

加藤耕太さんの「Pythonクローリング&スクレイピング[増補改訂版]」
を進めているのですが、

CSVファイル内にurl,titleに続くサイト内のURLとタイトルが保存されません。
簡単な事だと思うのですが、解決方法を教えていただきたいです。
初めて質問を投稿する為作法等分かっていないのですが、よろしくお願いいたします。

発生している問題・エラー

url,title
#この下にURLとタイトルがずらーっと保存されるはずなのですが、、

該当するソースコード

#pythonによるスクレイピング
import csv
from typing import List #型ヒントのためにインポート

import requests
import lxml.html

def main():
    """
    メインの処理。fetch(), scrape(), save()の三つの関数を呼び出す。
    """

    url = "https://gihyo.jp/dp"
    html = fetch(url)
    books = scrape(html,url)
    save("books.csv", books)

def fetch(url: str) ->str: # ->は戻り値の型を表す。
    """
    引数urlで与えられたURLのWebページを取得する。
    WebページのエンコーディングはContent_Typeヘッダーから取得する。
    戻り値:str型のHTML
    """

    r = requests.get(url)
    return r.text #HTTPヘッダーから取得したエンコーディングでデコードした文字列を返す。

def scrape(html: str, base_url: str) -> List[dict]:
    """
    引数htmlで与えられたHTMLから正規表現で書籍の情報を抜き出す。
    引数base_urlは絶対URLに変換する際の基準となるURLを指定する。
    戻り値:書籍(dict)のリスト
    """

    books =[]
    html = lxml.html.fromstring(html)
    html.make_links_absolute(base_url) #全てのa要素のhref属性を絶対URLに変換する。

    #cssselect()メソッドで、セレクターに該当するa要素のリストを取得して、個々のa要素に対して処理を行う。
    #セレクターの意味:id="listbook"である要素 の直接の子である li要素 の直接の子である
    #  itemprop="url"という属性を持つa要素

    for a in html.cssselect('#listbook > li > a[itemprop="url"]'):
        #a要素のhref属性から書籍のURLを取得する。
        url = a.get("href")

        #書籍のタイトルは itemprop="name" という属性を持つp要素から取得する。
        p = a.cssselect('p[itemprop="name"]')[0]
        title = p.text_content() #wbr要素などが含まれているのでtextではなくtext_content()を使う。

        books.append({"url": url, "title": title})

    return books

def save(file_path: str, books: List[dict]):
    """
    引数booksで与えられた書籍のリストをCSV形式のファイルに保存する。
    ファイルのパスは引数file_pathで与えられる。
    戻り値:なし
    """

    with open(file_path, "w", newline="") as f:
        #第1引数にファイルオブジェクトを、第2引数にフィールド名のリストを指定する。
        writer = csv.DictWriter(f, ["url","title"])
        writer.writeheader() #1行目のヘッダーを出力する。
        #writerows()で複数の行を一度に出力する。引数は辞書のリスト。
        writer.writerows(books)

#pythonコマンドで実行された場合にmain()関数を呼び出す。これはモジュールとして他のファイルから
#インポートされたときに、main()関数が実行されないようにするための、pythonにおける一般的なイディオム
if __name__ == "__main__":
    main()
0

1Answer

html.cssselect('#listbook > li > a[itemprop="url"]') の listbook を listBook に直してください。 B は大文字です。


自力で解決するにはスクリプトのどこまで正しく動いているか、データはどうなっているかを確認するため、ところどころに print を挿入するといいですよ。

    html = fetch(url)
    books = scrape(html,url)
    print(f"books: {books}")
    save("books.csv", books)

このようにすればbooks: [] と表示され、問題が save() より前にあることが分かるはずです。そうなると可能性は fetch() で取ってきた HTML がおかしいか、 HTML は正しいが scrape() が要素を見つけられないかの2つです。

さらに htmla を print してみて、 HTML は正しそうだが a が print されない、つまり for 文が一度も実行されていないことに気づけば、 html.cssselect('#listbook > li > a[itemprop="url"]') が空のリストを返していることに行き着きます。

続いて CSS セレクタの絞り込みをゆるめるように print(html.cssselect('#listbook > li'))print(html.cssselect('#listbook')) と試してみると ID が listbook の要素からして見つからないことが分かります。

1Like

Comments

  1. @Sucre1108

    Questioner

    正常に実行できました!ありがとうございます!
    その上これからの解決法まで教えていただけるなんて、、
    本当にありがとうございます!
    一人で悩み続けていたのですが、勇気を持って質問して見て良かったです!
  2. また何かあれば気軽に質問してくださいね。

    作法については十分だと思います。質問に書いてあると回答の手がかりになる情報は、問題のコード、エラーの内容、正しく動いたとしたらどういう結果になってほしいのか、参考にした本や記事、 OS やツールのバージョン、実行した手順などですが、この質問にはほぼほぼ揃っていました。

    タイトルは「PythonでスクレイピングしたデータをCSVに保存したが空になる」のようにやったことと問題を含めるといいです。

    質問に含めるコードは、可能なかぎり回答者の手元でそのまま実行できる形にしつつ、問題と関係ない部分をそぎ落としてみてください。たとえば今回のコードで books が空になるところまで自分で分かったとしたら、 save 関数を消して print(books) に置き換える感じです。問題と関係ない部分を削る考え方は自力でのデバッグにも役立ちます。
  3. @Sucre1108

    Questioner

    はい!ありがとうございます!

    本当に身になる話ばかりで感激です、、
    次回から質問する時に意識してやってみます!

Your answer might help someone💌