Help us understand the problem. What is going on with this article?

日向坂46 公式ブログのスクレイピング

日向坂46のブログ記事のスクレイピングを行い、データベースに保存を行う。
データベースの使用例として、画像の保存も行った。

今後は、乃木坂46と欅坂46のブログについても同様のことを行えるように拡張したいと考えている。

Pythonは少し触ったことがあるが、スクレイピングとデータベースに関しては初めてだ。
類似しているQiitaの記事を参考にしながら進めていこうと思う。

プログラムコード全文は最後に記載している。

実行環境

Google Colaboratory
Python 3.6.9

データベース

データベースにはSQLite3を使用した。

テーブル定義

各メンバーの情報を格納するテーブル

登録情報
- メンバーID
- メンバー名
- ブログ数
- 個人ページURL
code.py(一部抜粋)
sql = '''create table member
  (member_id integer primary key,
  member_name text,
  blog_count integer,
  page_url text)'''
con.execute(sql)

各ブログの情報を格納するテーブル

登録情報
- ブログID
- メンバーID
- メンバー名
- ブログタイトル
- ブログ更新日時
- 本文
- 画像URL
- 個別ページURL
code.py(一部抜粋)
sql = '''create table blog
  (blog_id integer primary key,
  member_id integer,
  member_name text,
  title text,
  uploaded_at text,
  body text,
  image_urls blob,
  page_url text)'''
con.execute(sql)

スクレイピング

日向坂46 メンバーの情報の取得

code.py(一部抜粋)
def get_members():
    member_list = {}
    response = requests.get('https://www.hinatazaka46.com/s/official/diary/member?ima=0000')
    soup = BeautifulSoup(response.text, 'lxml')

    members = soup.find_all("a", class_="p-blog-face__list")

    for member in members:
        # スペースと改行を削除
        member_name = member.text.replace(" ", "")
        member_name = member_name.replace("\n", "")

        # ブログの0ページ目のurlも取得(1ページ以降はこれに"&page=n"を追加
        member_list[member_name] = "https://www.hinatazaka46.com" + member.attrs["href"]

    return member_list

# メンバーリスト作成
members_list = get_members()

これにより、以下のような情報を取得できる。

メンバーの名前と、各メンバーの個別ブログページのURLである。
URL最後に個別の番号が振られているようなので、こちらをメンバーIDとして使用する。

結果
2 潮紗理菜 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=2
4 影山優佳 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=4
5 加藤史帆 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=5
6 齊藤京子 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=6
7 佐々木久美 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=7
8 佐々木美玲 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=8
9 高瀬愛奈 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=9
10 高本彩花 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=10
11 東村芽依 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=11
12 金村美玖 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=12
13 河田陽菜 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=13
14 小坂菜緒 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=14
15 富田鈴花 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=15
16 丹生明里 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=16
17 濱岸ひより https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=17
18 松田好花 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=18
19 宮田愛萌 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=19
20 渡邉美穂 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=20
21 上村ひなの https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=21
1000 日向坂46新三期生 https://www.hinatazaka46.com/s/official/diary/member/list?ima=0000&ct=1000

各メンバーの個別ページから各ブログの情報を抽出

タイトル 抽出

code.py(一部抜粋)
a = soup.find_all('div', class_='c-blog-article__title')

18番目のタイトルは、タイトル無しだった。

結果
0 make up ︎☺︎
1 元気です(   ᷇࿀ ᷆  )
2 にんじん!!
3 しっかり覚えました🐈
4 25,977🔎
5 暇を持て余したワタナベの遊び
6 改編
7 観てくれたかな?
8 ㊗️1歳
9 ひらがなからはじめよう☀️
10 また会う日まで!
11 やぁ!‪(   ᷇࿀ ᷆  )‬
12 どうも‪(   ᷇࿀ ᷆  )‬
13 20歳になりました㊗️
14 フリとオチが効き過ぎた結果
15 ひよたんとドーナツ半分こ🍩
16 ちょきちょき✂️
17 本物かぁ
18 
19 節分の日は恵方巻きを食べて豆まきもしました。ところで、私の甥っ子は豆が大好きなのです。

ブログ更新日時 抽出

code.py(一部抜粋)
b = soup.find_all('div', class_='c-blog-article__date')
結果
0 2020.5.3 23:38
1 2020.4.26 00:30
2 2020.4.20 22:53
3 2020.4.17 14:50
4 2020.4.13 21:01
5 2020.4.10 10:04
6 2020.4.7 17:57
7 2020.4.4 00:17
8 2020.3.29 01:09
9 2020.3.27 00:17
10 2020.3.22 23:34
11 2020.3.19 01:31
12 2020.3.14 14:34
13 2020.2.25 18:21
14 2020.2.22 01:52
15 2020.2.21 16:04
16 2020.2.19 23:21
17 2020.2.16 21:09
18 2020.2.14 22:13
19 2020.2.4 08:54

メンバー名 抽出

code.py(一部抜粋)
c = soup.find_all('div', class_='c-blog-article__name')
結果
0 渡邉 美穂
1 渡邉 美穂
2 渡邉 美穂
3 渡邉 美穂
4 渡邉 美穂
5 渡邉 美穂
6 渡邉 美穂
7 渡邉 美穂
8 渡邉 美穂
9 渡邉 美穂
10 渡邉 美穂
11 渡邉 美穂
12 渡邉 美穂
13 渡邉 美穂
14 渡邉 美穂
15 渡邉 美穂
16 渡邉 美穂
17 渡邉 美穂
18 渡邉 美穂
19 渡邉 美穂

本文 抽出

code.py(一部抜粋)
d = soup.find_all('div', class_='c-blog-article__text')

本文は長すぎるため、割愛する。

結果
割愛

個別ページURL 抽出

code.py(一部抜粋)
# 個別ページURL
e = soup.find_all('a', class_='c-button-blog-detail')

# ブログID
f = e.split('/')[5].split('?')[0]

ブログの個別ページのURL中にブログナンバーらしきものがあるため、こちらをブログIDとする。

結果
0 /s/official/diary/detail/33637?ima=0000&cd=member
33637
1 /s/official/diary/detail/33511?ima=0000&cd=member
33511
2 /s/official/diary/detail/33442?ima=0000&cd=member
33442
3 /s/official/diary/detail/33384?ima=0000&cd=member
33384
4 /s/official/diary/detail/33325?ima=0000&cd=member
33325
5 /s/official/diary/detail/33274?ima=0000&cd=member
33274
6 /s/official/diary/detail/33227?ima=0000&cd=member
33227
7 /s/official/diary/detail/33122?ima=0000&cd=member
33122
8 /s/official/diary/detail/33046?ima=0000&cd=member
33046
9 /s/official/diary/detail/33032?ima=0000&cd=member
33032
10 /s/official/diary/detail/32997?ima=0000&cd=member
32997
11 /s/official/diary/detail/32961?ima=0000&cd=member
32961
12 /s/official/diary/detail/32903?ima=0000&cd=member
32903
13 /s/official/diary/detail/32736?ima=0000&cd=member
32736
14 /s/official/diary/detail/32702?ima=0000&cd=member
32702
15 /s/official/diary/detail/32695?ima=0000&cd=member
32695
16 /s/official/diary/detail/32682?ima=0000&cd=member
32682
17 /s/official/diary/detail/32650?ima=0000&cd=member
32650
18 /s/official/diary/detail/32623?ima=0000&cd=member
32623
19 /s/official/diary/detail/32519?ima=0000&cd=member
32519

画像URL 抽出

code.py(一部抜粋)
# 画像url
g = res_s.find('div', class_='c-blog-article__text').find_all('img')
g = [url["src"] for url in g]

画像URLの抜き出し、配列に保存している。

結果
割愛

データ投入

紹介していない変数もあるが、このようにデータベースに取得したデータを投入していっている。

code.py(一部抜粋)
sql = "insert into blog values(?, ?, ?, ?, ?, ?, ?, ?);"
cur.execute(sql, (x[5], member_id, name, x[0], x[1], x[3], pic_urls, x[4]))

結果(データベース)

データベースの中身を見ることのできるソフトを使用した。

こちらはデータベースの構造を表示している。
S__91750409.jpg

こちらはメンバーを登録してるテーブルを表示している。
S__91750411.jpg

こちらは全メンバーのブログ情報を登録しているテーブルを表示している。
S__91750412.jpg

番外編 -画像保存-

データベースに登録されている画像URLの文字列を取得し、その画像URLから画像を保存していく。

保存を行っていくメンバーを指定し、その後は画像保存を自動で行っていく。

以下の画像では、
- メンバーIDとメンバー名の表示
- 保存を行いたいメンバーのIDを入力

という部分が表示されている。

S__91750407.jpg

以下のコードで、画像の保存を行っている。

code.py(一部抜粋)
from datetime import datetime as dt

#-省略-

sql="select * from blog where member_id=" + mem_id
for x in cur.execute(sql):
  name = x[2]
  # 日付を yymmdd の形式にする
  datename = dt.strptime(x[4], '%Y.%m.%d %H:%M').strftime("%y%m%d")
  pic_urls = x[6]

  print(name)
  print(datename)

  for i, url in enumerate(pic_urls.split(",")):
    # メンバー名/日付_番号.jpg
    file_name = name + "/" + datename + "_" + str(i) + ".jpg"

    # 同ファイル名の存在判定
    if os.path.isfile(file_name) == False:
      # URLから画像の取得
      response = requests.get(url)
      image = response.content
      time.sleep(5)

      # 画像を指定フォルダに保存
      with open(file_name, "wb") as aaa:
        aaa.write(image)

  print("-------------------------------------------------")

結果

このようにGoogleドライブ内に画像の保存が行われていく。

S__91750408.jpg

最後に

突貫工事のようなプログラムの書き方と、Qiita記事の書き方をしてきました。
そのため、非常に内容が伝わりにくいかと思います。
申し訳ないです。

今後はプログラム、Qiita記事を見やすく編集を行っていきます。
その後、ブログのテキストを使用した言語処理を行っていきたいと考えています。

質問などあれば、コメントよろしくお願いします。

参考サイト

構成

データベースを組み立てるに当たり、全体の構成を参考にした。

今回しようとしていることの大まかな部分が一致している記事。
コードの細かな説明と手順が分かりやすい。

BeautifulSoup

スクレイピングのコードを参考にした。

BeautifulSoupを使用するに当たって、基本的なコードを勉強。

BeautifulSoupにて、classを指定して要素を取得する方法を使用した。

SQLite3

ColaboratoryでSQLite3を使用するに当たり、最初の準備

最初の勉強として、理解しやすかった

SQLite3の基本操作プログラムコード

タイトルの通り。
利用可能なデータ型と、記述方法について。

Code 全文

データベース テーブル作成

code_1.py
from bs4 import BeautifulSoup
import requests
import time
import sqlite3

drive_path = "/content/drive/My Drive/WorkSpace/python/data/"
db_name = "hinata_blog.db"

# memberテーブル
def make_table_member(db_path):

  con = sqlite3.connect(db_path)
  # Cursorオブジェクトを作成
  cur = con.cursor()

  # テーブルの削除
  #sql="drop table blog"
  #con.execute(sql)

  # テーブル作成
  sql = '''create table member
    (member_id integer primary key,
    member_name text,
    blog_count integer,
    page_url text)'''
  con.execute(sql)

  #con.comit()
  con.close()
  return

make_table_member(drive_path+db_name)

# blogテーブル
def make_table_blog(db_path):

  con = sqlite3.connect(db_path)
  # Cursorオブジェクトを作成
  cur = con.cursor()

  # テーブルの削除
  #sql="drop table blog"
  #con.execute(sql)

  # テーブル作成
  sql = '''create table blog
    (blog_id integer primary key,
    member_id integer,
    member_name text,
    title text,
    uploaded_at text,
    body text,
    image_urls blob,
    page_url text)'''
  con.execute(sql)

  #con.comit()
  con.close()
  return

make_table_blog(drive_path+db_name)

# スクレイピング&データ投入
# memberテーブル
def get_members():
    member_list = {}
    response = requests.get('https://www.hinatazaka46.com/s/official/diary/member?ima=0000')
    time.sleep(5)

    soup = BeautifulSoup(response.text, 'lxml')

    members = soup.find_all("a", class_="p-blog-face__list")

    for member in members:
        # スペースと改行を削除
        member_name = member.text.replace(" ", "")
        member_name = member_name.replace("\n", "")

        # ブログの0ページ目のurlも取得(1ページ以降はこれに"&page=n"を追加
        member_list[member_name] = "https://www.hinatazaka46.com" + member.attrs["href"]

    return member_list

# メンバーリスト作成
members_list = get_members()

スクレイピング&データ投入

code_2.py
from bs4 import BeautifulSoup
import requests
import time
import sqlite3

drive_path = "/content/drive/My Drive/WorkSpace/python/data/"
db_name = "hinata_blog.db"
db_path = drive_path+db_name

# スクレイピング&データ投入
# blogテーブル
def get_text(member_data, db_path):

  page_number = 0
  url = member_data[3]

  while True:
    print(url + "&page=" + str(page_number))
    response = requests.get(url + "&page=" + str(page_number))
    time.sleep(5)
    soup = BeautifulSoup(response.text, 'lxml')
    res = soup.find_all('div', class_='p-blog-article')

    if res != []:
      result = []
      for res_s in res:
        # タイトル
        a = res_s.find('div', class_='c-blog-article__title').text.strip()
        # ブログ更新日時
        b = res_s.find('div', class_='c-blog-article__date').text.strip()
        # メンバー
        c = res_s.find('div', class_='c-blog-article__name').text.strip()
        # 本文
        d = res_s.find('div', class_='c-blog-article__text').text.strip()
        # 画像url
        g = res_s.find('div', class_='c-blog-article__text').find_all('img')
        g = [url["src"] for url in g]
        # 個別ページURL, ブログID
        e = res_s.find('a', class_='c-button-blog-detail').get('href')
        f = e.split('/')[5].split('?')[0]
        x = [a,b,c,d,e,f,g]
        result.append(x)

      #insert_blog(result, db_path, member_data)
      member_id = member_data[0]
      name = member_data[1]

      end_point = False

      for x in result:
        #con = sqlite3.connect(db_path)
        # Cursorオブジェクトを作成
        #cur = con.cursor()

        pic_urls = ','.join(x[6])

        try:
          sql = "insert into blog values(?, ?, ?, ?, ?, ?, ?, ?);"
          cur.execute(sql, (x[5], member_id, name, x[0], x[1], x[3], pic_urls, x[4]))
          con.commit()
        except:
          end_point = True
          break
        #con.close()

      if end_point == True:
        break

      page_number += 1
    else:
      break
  return


con = sqlite3.connect(db_path)
# Cursorオブジェクトを作成
cur = con.cursor()
sql="select * from member"
scarp_mem = [mem for mem in cur.execute(sql)]
for member_data in scarp_mem:
  print(member_data)
  get_text(member_data, db_path)
  name = member_data[1]
  sql="select count(*) from blog where member_name=" + "'" + name + "'"
  cur.execute(sql)
  for blog_count in cur:
    blog_c = blog_count[0]
  sql="update member set blog_count=" + str(blog_c) + " where member_name=" + "'" + name + "'"
  cur.execute(sql)

con.commit()
con.close()

画像保存先のフォルダ作成

code_3.py
import os
import sqlite3

drive_path = "/content/drive/My Drive/WorkSpace/python/data/"
db_name = "hinata_blog.db"
db_path = drive_path+db_name

pic_path = "/content/drive/My Drive/WorkSpace/python/save_picture/日向坂46/"

con = sqlite3.connect(db_path)
# Cursorオブジェクトを作成
cur = con.cursor()
sql="select * from member"
scarp_mem = [mem for mem in cur.execute(sql)]
mem_dict = {}
for member_data in scarp_mem:
  mem_dict[member_data[0]] = member_data[1]

for num in mem_dict:
  print(num, '\t', mem_dict[num])
  os.mkdir(pic_path+mem_dict[num])

画像保存

code_4.py
import os
from datetime import datetime as dt
from bs4 import BeautifulSoup
import requests
import time
import sqlite3

drive_path = "/content/drive/My Drive/WorkSpace/python/data/"
db_name = "hinata_blog.db"
db_path = drive_path+db_name

pic_path = "/content/drive/My Drive/WorkSpace/python/save_picture/日向坂46/"

# 画像の保存
def main():
  con = sqlite3.connect(db_path)
  # Cursorオブジェクトを作成
  cur = con.cursor()

  # memberテーブルの情報を取得
  sql="select * from member"
  scarp_mem = [mem for mem in cur.execute(sql)]

  # 取得情報を辞書型で保存  
  mem_dict = {data[0]:data[1] for data in scarp_mem}

  # 辞書型をprint表示
  for num in mem_dict:
    print(num, '\t', mem_dict[num])

  print("\n画像保存を行うメンバーIDの入力")
  mem_id = input()

  print(mem_dict[int(mem_id)])
  print("-------------------------------------------------")

  print("START")
  print("-------------------------------------------------")

  sql="select * from blog where member_id=" + mem_id
  for x in cur.execute(sql):
    name = x[2]
    datename = dt.strptime(x[4], '%Y.%m.%d %H:%M').strftime("%y%m%d")
    pic_urls = x[6]

    print(name)
    print(datename)

    print(pic_urls.split(","))

    for i, url in enumerate(pic_urls.split(",")):
      if url == '':
        continue
      # メンバー名/番号.jpg
      file_name = pic_path + name + "/" + datename + "_" + str(i) + ".jpg"

      if os.path.isfile(file_name) == False:
        response = requests.get(url)
        image = response.content
        time.sleep(5)

        with open(file_name, "wb") as aaa:
          aaa.write(image)

    print("-------------------------------------------------")

  return

main()
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした