ひさふぃの日記

DjangoとPythonとLaravelが好き。大阪でフリーランスエンジニアやってます。

PythonによるWebスクレイピングについて

現在作成(絶賛どハマり)中のwebスクレピングサービス、そのコードについて簡単にまとめていきます。本来は2016年以内に公開しました、ドヤァ!という予定だったのですが、公開直前のサーバー構築につまづいてハマること3週間、その間にVPSを初期化すること十数回。本当にLinuxは奥が深いです。

shikouno.hatenablog.com

yumyum...と何回も叩いたのでそろそろ日本語を打ちたい・未来の自分が成長を実感するためのメモ、以上の目的のために記事を書きます。そのため、行き当たりばったりの欠陥コードをそのまま掲載しますので要注意です。参考にされることはないと思いますが、念のため。

では、今回作った目的とコードを示して簡単にコメントしていきます。 

 今回作ったものについて

ハウツー食材(仮):検索した食材のレシピを引用して、写真と必要食材を一覧表示するものです。

買い物に行って目新しい食材を見つけても調理法がわからずに購入しないことがかなりあります。レシピサイトで検索して一件ずつ見ていけばいいのですがそれは少し面倒なので、新しいもの好きな私は歯がゆい思いをしておりました。

そういう訳で、webスクレイピング・公開の勉強も兼ねてサクッと年内に作ろうと12月頃から取り組みました。

コード

 以前の記事と基本的にやっていることはほぼ変わりません。

shikouno.hatenablog.com

getメソッドを実行することで、検索した食材に関するhtmlが生成されます。

次の三つのメソッドでレシピサイトから情報の成形を行なっています。また、各処理のタグは引用元に使わせてもらった三つのサイト〜オレンジページ・レシピブログ・楽天レシピ〜に合わせています。

#get search url

引用元のレシピサイトで食材検索して一覧を表示、各レシピのURLを抽出する。

#get contents and make html

抽出したURLから必要な情報を抽出する。今回は、レシピ名・何人前・食材・写真を次の処理に渡せるようにして、各メソッドの後ろ2行にてhtmlを生成(後述)しています。

class Get:

    def __init__(self):
        self.orange_content = ""
        self.recipeblog_content = ""
        self.rakuten_content = ""

    def get(self, word):
        self.get_orange(word)
        self.get_recipeblog(word)
        self.get_rakuten(word)
        self.make_html()

    def getsoup(self, url):
        req = requests.get(url)
        req.encoding = "utf-8"
        return BeautifulSoup(req.text,"lxml")

    def get_orange(self, word):
        #get search url
        search_url = ("http://www.orangepage.net/recipes/search?utf8=✓\
&search_recipe%5Bkeyword%5D={}&searchBtn1=".format(word))
        soup = self.getsoup(search_url)
        tag1 = soup.find("div", {"class":"horizontal3Col"})
        tag2 = tag1.find_all('a',href=re.compile("/recipes/detail"))
        url_list = [j for i,j in enumerate(tag2) if i%2 ==0]
        #get contents and make html
        for i in url_list:
            content = []
            url = 'http://www.orangepage.net{}'.format(i["href"])
            url_get = self.getsoup(url)
            img = url_get.find("img", {"itemprop":"photo"})
            numbers = url_get.find("h2")
            name = url_get.find_all("span" , {"itemprop":"name"})
            amount = url_get.find_all("span" , {"itemprop":"amount"})
            numbers = numbers.text
            numbers = numbers[5:-1]
            content += self.make_content(url, img, numbers, name, amount)
            self.orange_content += self.make_htmltable(content)

    def get_recipeblog(self, word):
        search_url = ("http://www.recipe-blog.jp/search/recipe?keyword={}\
&type=recipe&sort_order=score".format(word))
        soup = self.getsoup(search_url)
        tag = soup.find("div", {"class":"searchListArea"})
        tag2 = tag.find_all('a',href=re.compile("profile"))
        url_list = [j for i,j in enumerate(tag2) if i%3 ==0]
        for i in url_list:
            content = []
            url_get = self.getsoup(i["href"])
            img = url_get.find("img", {"class":"photo"})
            numbers = url_get.find("span", {"class":"yield"})
            name = url_get.find_all("span" , {"class":"name"})
            amount = url_get.find_all("span" , {"class":"amount"})
            if numbers == None:
                numbers = url_get.find("h2", {"class":"mainTitle"})
                if numbers == None:
                    numbers = "人数表記なし"
                else:
                    numbers = numbers.text
            else:
                numbers = numbers.text
            content += self.make_content(i["href"], img, numbers, name, amount)
            self.recipeblog_content += self.make_htmltable(content)

    def get_rakuten(self, word):
        search_url = ("https://recipe.rakuten.co.jp/search/{}/".format(word))
        soup = self.getsoup(search_url)
        url_list = soup.find_all('a',href=re.compile("/recipe/"))
        for i in url_list:
            content = []
            url = 'http://recipe.rakuten.co.jp{}'.format(i["href"])
            url_get = self.getsoup(url)
            img = url_get.find("img", {"itemprop":"image"})
            numbers = url_get.find("span", {"itemprop":"recipeYield"})
            name = url_get.find_all("a" , {"class":"name"})
            amount = url_get.find_all("p" , {"class":"amount"})
            numbers = numbers.text
            content += self.make_content(url, img, numbers, name, amount)
            self.rakuten_content += self.make_htmltable(content)

本当は各サイトごとのメソッドを作成するのではなく、汎用的なメソッドを一つ作成して引数で使い分けたかったのですが断念しました。スッキリかける方法があるのでしょか。それとも、スクレイピングする場合って各サイトごとにタグの指定とか設定しているんですかね。

 

あとは、上記で抽出した情報を元にhtmlを生成していきます。最初の二つのメソッドは各サイトのコンテンツを作成する際に実行しており、最後のメソッドは本記事の一番最初のgetメソッドにて実行します。

    def make_content(self, url, img, numbers, name, amount):
        content, table = [], ""
        for i, j in zip(name,amount):
            table += ("""    <li class="ingredients">
    <div class="name_amount">{} : {}</div>""".format(i.text,j.text))
        title, image = img['alt'], img['src']
        numbers = "(" + numbers + ")"
        content = [url, title, numbers, image, table]
        return content

    def make_htmltable(self, content):
        html_table =("""
 <tr>
  <td>
   <a style="width:100%; height:100%;" href="{0[0]}">
    <div>{0[1]} {0[2]}</div>
    <ul>
    {0[4]}
    </ul>
    <div align="center">
     <img src="{0[3]}">
    </div>
   </a>
  </td>
 </tr>
                    """.format(content))
        return html_table

    def make_html(self):
        with open("recipecite.html", "w", encoding="utf-8") as f:
            f.write("""
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ハウツー食材</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
  </head>
  <body>
   <div class="container-fluid">
    <div class="row">

  <div class="col-xs-12 col-md4 bg-warning">
   <table class="table table-bordered table-hover">
     <tr>
      <th>オレンジページ</th>
     </tr>
     {}
   </table>
  </div>
//以下、レシピブログ・楽天レシピも同じ//

    </div>
   </div>
  </body>
</html>
""".format(self.orange_content, self.recipeblog_content, self.rakuten_content))

 

Bootstrapのグリッドを使ってみたかったため、下記のサイトを参考にさせてもらいました。

HTMLのテンプレート

デザイン知識がなくてもOK!Bootstrapの使い方【入門者向け】 | TechAcademyマガジン

Bootstrapの概要

とほほのBootstrap入門

 

終わりに

以前と同じことをやっておりコード自体も短いため、作成はサクッとできました。しかし、htmlの生成までに30秒程かかるという致命的な欠陥が存在しています。そのため、Djangoに放り込んでherokuで公開してみたのですが、timeout制限により残念な結果になりました。そして、設定いじれるVPSなら公開できるだろう!から、サーバー構築どハマりにつながります。Linuxの勉強になってるので結果的には良かったですけどね!

とはいえ、このままLinuxばかり触っていると迷宮入りしそうなので、pythonのコード自体を高速化したいですね。socket_recvというのが律速で未知の領域っぽいので、ぼちぼち付き合っていきます。

あとは、スクレイピングのコードばかり書いていたので他の勉強もしていきたいです。pythonを使ったものに限らず、Linux・綺麗なコードの書き方とか。

果てなく学ぶことがあるプログラミング、面白いです!