【Python】風俗嬢のデータベース作成
今回、風俗分析に使用しているデータベースの作成プログラムについてご紹介します。データベースといってもBigquery等で運用する大規模なものではなく、CSVファイルへ出力する以下のものになります。
取得元はデリヘルタウン。こちらに掲載されている女の子情報をPythonのスクレイピングによりデータ収集し、CSVファイルとして出力しています。PythonのソースコードはぽちゃPRESSさんのものをベースに、少しだけ手を加えたものです。以下の部分が変わっています。
・ログ出力機能の追加
・スクレイピング手法をrequests.getに変更
・ノイズ条件の変更
◇Main関数
ロギングの設定とデータベース作成のための各関数の呼び出しを行います。データベースは以下の手順で作成します。
①地域別のお店一覧ページのURLを取得
②各お店の女の子一覧ページのURLを取得
③各お店の女の子一覧ページのHTMLを取得
④HTMLから女の子情報を抽出
from gen_list import *
import logging as lg
import time
import os
#LOG初期設定
if not os.path.exists('log'):
os.mkdir('log')
formatter = '%(levelname)s : %(asctime)s : %(funcName)s : %(lineno)d : %(message)s'
lg.basicConfig(filename='log/logger' + str(time.time()) + '.log', format=formatter, level=lg.INFO)
#デリヘルタウンのエリア別ページの全URLを取得、txtで保存
gen_area_list()
#全ショップURLを取得、txtで保存
gen_shop_list()
#女の子一覧情報を取得、htmlで保存
download_gal_html()
#女の子一覧ページから女の子情報を取得して、データベースを作成する
gen_database()
①地域別のお店一覧ページのURLを取得
まず、掲載されているデリヘル店の全URLを取得します。デリヘルタウンでは地域別ページの階層化に各店舗ページのURLが一覧で掲載されているため、すべての地域に掲載されているお店リストを取得すれば、デリヘルタウンに掲載する店舗ページが網羅できる。
取得した地域別のお店一覧ページはテキストファイルに出力します。
テキストファイルに出力せずリストに入れておいて②の処理を行えば良いのではないか、と思われるかもしれませんが、もちろんそれでもOKです。
def gen_area_list():
from requests_html import HTMLSession
import re
import os
# デリヘルタウントップのURL
url_dto = 'https://www.dto.jp'
# デリヘルタウントップページの情報取得
session = HTMLSession()
r = session.get(url_dto)
# 以下、ファイルへの書き出し
if not os.path.exists('list'):
os.mkdir('list')
f = open('list/area_list.txt', 'w')
for link in r.html.links:
# 取得したURLから、都道府県を表すURLだけ選別して書き出す。
# 取得するURLの例:「/hokkaido」とか「/aomori」とか。スラッシュの数で判別する
is_main_area = (len(link.split('/')) == 2)
if (is_main_area):
# https://www.dto.jp/を頭につけてファイル書き出し
f.write(url_dto + link + '/shop-list' + '\n')
# 取得するURLの例:「/mito/shop-list」とか「/hakata/shop-list」とか
is_sub_area = re.search('/*/shop-list', link)
if (is_sub_area):
# https://www.dto.jp/を頭につけてファイル書き出し
f.write(url_dto + link + '\n')
②各お店の女の子一覧ページのURLを取得
①で取得した各地域の店舗リストから店舗ページにアクセスして、女の子一覧ページのURLを取得します。店舗ページと女の子一覧ページはURLの規則性から判断しています。 ※詳しくは以下ソースコードをご覧ください。
なお、今回の処理では掲載されているデリヘル店数分(約7000回)アクセスするため、デリヘルタウンのサーバに負荷がかからないよう1回のアクセス毎に1秒sleepしています。そのため時間がかかります。。
def gen_shop_list():
# 本プログラムの目的:
# 「各地域の店舗リスト」をもとにして、
# 全登録店舗のURLを取得する
from requests_html import HTMLSession
import re
import time
# 本プログラムは実行に時間がかかる。
# そのため、進捗をコンソール上に表示するための変数を用意
num_current = 0
num_shops = sum(1 for line in open('list/area_list.txt'))
# デリヘルタウントップのURL
url_dto = 'https://www.dto.jp'
# get_area_listで作った各地域の店舗リスト
rf = open('list/area_list.txt', 'r')
# 以下、全店舗のURLをリストに詰め込む
shop_list = []
for line in rf:
# 店舗リストから各店舗のURL取得
session = HTMLSession()
r = session.get(line.strip())
for link in r.html.links:
# 取得したURLから、店舗のURLだけ選別して書き出す。
# 取得するURLの例:「/shop/30552」とか「/shop/29517」とか
is_shop = re.search('/shop/', link)
if (is_shop):
# https://www.dto.jp/を頭につけてリストに追加
shop_list.append(url_dto + link)
# サーバに負荷をかけないよう、1つのページにアクセスするごとに1秒停止
time.sleep(1)
# 現在の進捗を表示
num_current += 1
print('\r' + '現在の進捗: %03d / %03d ' % (num_current, num_shops), end='')
# 以下、ファイルへの書き出し
# shop_listには、店舗のURLを片っ端からぶち込んでいる。よってURLが重複していることがある。
# そのため、setコマンドによって重複をなくす。
shop_list = list(set(shop_list))
# 書き出し先のファイル
wf = open('list/shop_list.txt', 'w')
# リストをファイルに書き出し
for shop in shop_list:
wf.write(shop + '\n')
③各お店の女の子一覧ページのHTMLを取得
②で取得した各店舗の女の子一覧ページにアクセスし、女の子一覧ページのHTMLソースをローカルに保存します。判定は何もせず片っ端から女の子ページへアクセス→HTML形式で保存 を繰り返します。この工程で欲しい情報がローカルに保存されますので、今後の処理が自分のPC内だけで完結でき、とても扱いやすくなります。この処理でも約7000回アクセスするため、1回のアクセス毎に2秒sleepします。この処理は数時間かかりますので気長に待ってください。
def download_gal_html():
# 本プログラムの目的:
# 全店舗のトップページURLをもとに「女の子一覧」ページにアクセスし、
# 「女の子一覧」ページのソースを自分のPCにコピーする
import urllib.request
from urllib.error import URLError, HTTPError
import requests
import re
import time
import os
import logging as lg
# 出力先のディレクトリ確認。なければ作成。
if not os.path.exists('htmls'):
os.mkdir('htmls')
# 本プログラムは実行に時間がかかる。
# そのため、進捗をコンソール上に表示するための変数を用意
num_current = 0
num_shops = sum(1 for line in open('list/shop_list.txt'))
# 全店舗のトップページURLリストを開く
shop_top_urls = open('list/shop_list.txt', 'r')
for shop_top_url in shop_top_urls:
shop_id = re.sub("\\D", "", shop_top_url)
# 店舗のトップページのURLをもとに、
# 「女の子一覧」ページのURLを生成
# 例:https://s.dto.jp/shop/16721/gals
gals_url = shop_top_url.strip() + '/' + 'gals'
# 「女の子一覧」ページのhtmlを文字列で取得
try:
# rf = urllib.request.urlopen(url=gals_url)
rf = requests.get(url=gals_url)
except HTTPError as e:
lg.error('%s : %s', str(e), str(shop_top_url))
continue
except URLError as e:
lg.error('%s : %s', str(e), str(shop_top_url))
continue
# tmp = rf.read() # 「tmp」はbytes型の文字列になる
# html = tmp.decode("utf-8") # str型に変換して変数「html」に格納
html = rf.text
rf.close()
# URLから数字(店舗ID)を抜き出す。書き出しファイルの名前指定用。
shop_id = re.sub("\\D", "", gals_url)
# 書き出し先のファイルを作成。ファイル名は「htmls/(店舗ID).html」とする
# 例:htmls/16721.html
wf = open('htmls/' + shop_id + '.html', 'w', encoding='utf-8')
# ファイルへの書き出し
wf.write(html)
wf.close()
# デリヘルタウンのサーバに負荷をかけないよう、1回アクセスしたら2秒待つ
time.sleep(2)
# 現在何店舗取得したかをコンソール上に表示
num_current += 1
print('\r' + '現在 %04d / %04d 店舗目を取得' % (num_current, num_shops), end='')
④HTMLから女の子情報を抽出
③で取得したHTMLソースから女の子情報や在籍する店舗情報を抽出して、データフレームに格納します。抽出はトライ&エラーで手探り感満載です。詳しくはソースコードをご覧ください。ここの有効な情報の判定で、ぽちゃPRESSさんのものに多少手を加えていきます。
# 1行分のdataframeに変換する関数
def to_gal_df(gal_infos, prefecture, shop_name, shop_sub_info):
import pandas as pd
import re
# 1店舗分の女の子のdataframe
gal_df_1shop = pd.DataFrame(
index=[],
columns=['名前', '年齢', '身長',
'バスト', 'カップ', 'ウエスト', 'ヒップ',
'一言説明', '都道府県', '店舗名', '店舗種類']
)
for gal_info in gal_infos:
# 1行分のdataframeを作る
df = pd.DataFrame(
index=[0],
columns=['名前', '年齢', '身長',
'バスト', 'カップ', 'ウエスト', 'ヒップ',
'一言説明', '都道府県', '店舗名', '店舗種類']
)
try:
# split結果の例: ['', '咲歩(さほ)', '34歳/168cm/84(C)-58-87', '【敏感過ぎる身体】', ' 1', '']
gal_info_split = gal_info.text.split('\n')
# 名前の格納
df['名前'][0] = gal_info_split[1]
# 歳、身長、スリーサイズの格納
# split後の例:['34歳', '168cm', '84', 'C', '', '58', '87']
gal_spec = re.split('[/()-]', gal_info_split[2])
df['年齢'][0] = int(re.sub("\\D", "", gal_spec[0]))
df['身長'][0] = int(re.sub("\\D", "", gal_spec[1]))
df['バスト'][0] = int(gal_spec[2])
# カップは書いている女の子と書いていない女の子がいるので処理分岐
if (gal_spec[4] == ''):
df['カップ'][0] = gal_spec[3]
df['ウエスト'][0] = int(gal_spec[5])
df['ヒップ'][0] = int(gal_spec[6])
else:
df['カップ'][0] = '-'
df['ウエスト'][0] = int(gal_spec[3])
df['ヒップ'][0] = int(gal_spec[4])
# 一言説明
df['一言説明'][0] = gal_info_split[3]
### prefecture_infoの処理
df['都道府県'][0] = prefecture.text.strip()
### shop_name_infoの処理
df['店舗名'][0] = shop_name.text.strip()
### shop_sub_infoの処理
shop_sub_info_split = re.split('[|【]', shop_sub_info.text)
shop_kind = shop_sub_info_split[1].split(' ')
df['店舗種類'][0] = shop_kind[1]
# 女の子のデータと判断したものはdataframeに追加
if (is_valid_df(df)):
gal_df_1shop = gal_df_1shop.append(df)
# 配列に合わないデータになる場合は例外
except:
continue
return gal_df_1shop
# 有効な情報かどうかを判定
def is_valid_df(df):
import logging as lg
removenames = ['割', 'キャンペーン', 'ツイッター', 'LINE',
'日記', '店長', '会員', 'メルマガ', 'WEB', '番長',
'デリバリー', '募集', 'プラン', 'おしらせ', 'お知らせ',
'交通費', '拡大', 'パック', '急募', '拡張']
for removename in removenames:
if removename in df['名前'][0]:
lg.info('名前:%s in %s',str(removename), str(df['名前'][0]))
return False
if (df['年齢'][0] < 15 or 100 < df['年齢'][0]):
lg.info('年齢:%s < 15 or 100 < %s', str(df['年齢'][0]), str(df['年齢'][0]))
return False
if (df['バスト'][0] < 40 or 300 < df['バスト'][0]):
lg.info('バスト:%s < 40 or 300 < %s', str(df['バスト'][0]), str(df['バスト'][0]))
return False
if (df['ウエスト'][0] < 40 or 300 < df['ウエスト'][0]):
lg.info('ウエスト:%s < 40 or 300 < %s', str(df['ウエスト'][0]), str(df['ウエスト'][0]))
return False
if (df['ヒップ'][0] < 40 or 300 < df['ヒップ'][0]):
lg.info('ヒップ:%s < 40 or 300 < %s', str(df['ヒップ'][0]), str(df['ヒップ'][0]))
return False
if(df['バスト'][0] == df['ウエスト'][0] and df['ウエスト'][0] == df['ヒップ'][0]):
lg.info('スリーサイズ:%s == %s == %s', str(df['バスト'][0]), str(df['ウエスト'][0]), str(df['ヒップ'][0]))
return False
if(df['身長'][0] == df['バスト'][0] or df['身長'][0] == df['ウエスト'][0] or df['身長'][0] == df['ヒップ'][0]):
lg.info('身長とスリーサイズ:%s : %s : %s : %s', str(df['身長'][0]), str(df['バスト'][0]), str(df['ウエスト'][0]), str(df['ヒップ'][0]))
return False
# すべてのチェックをパスしたらTrueを返す
return True
def gen_database():
# 本プログラムの目的:
# 「女の子一覧」ページのソースコードをもとに、
# 全デリヘル嬢のデータベースを作る
import bs4
import pandas as pd
import glob
import os
# ファイルリストを作る
file_paths = glob.glob('./htmls/*.html')
# 出力先のディレクトリ確認。なければ作成。
if not os.path.exists('database'):
os.mkdir('database')
# 進捗確認用の変数
count_shops = 0
num_shops = len(file_paths)
# 空のdataframeを作る
global gal_df
gal_df = pd.DataFrame(
index=[],
columns=['名前', '年齢', '身長',
'バスト', 'カップ', 'ウエスト', 'ヒップ',
'一言説明', '都道府県', '店舗名', '店舗種類']
)
for file_path in file_paths:
# 対象のhtmlファイルからsoupを作成
html = bs4.BeautifulSoup(open(file_path, encoding='utf-8'), 'html.parser')
gal_infos = html.find_all(class_="text") # 女の子のスペックのみを取り出してリストにする
prefecture_info = html.find(class_="logo") # このタグの中に都道府県が書いてある
shop_name_info = html.find(class_="header_shop_title") # このタグの中に店舗名が書いてある
shop_sub_info = html.find("title") # このタグの中に店舗種類が書いてある(人妻デリヘルとか)
# gal_infosから、1店舗分のdataframeを生成
df = to_gal_df(gal_infos, prefecture_info, shop_name_info, shop_sub_info)
# 1店舗分のdfをgal_dfのケツに追加。最終的にはgal_dfをファイルに書き出す
gal_df = gal_df.append(df)
count_shops += 1
print('\r' + '現在 %04d / %04d 店舗目を処理中' % (count_shops, num_shops), end='')
# 100店舗ごとにgal_dfをファイル書き出し(途中でプログラムをストップしても中間結果が残るように)
if (count_shops % 100 == 0):
gal_df.to_csv("database/gals_database.csv", encoding='utf_8_sig', index=None)
# 最終結果をファイル書き出し
gal_df.to_csv("database/gals_database.csv", encoding='utf_8_sig', index=None)
いかがでしたでしょうか。
今回、初めてPythonに触れてみたのて、しかも他社様のソースコードを少し手を加えた程度ですが、至らぬ点が多々あると思います。もっとこうしたほうが良い等のご指摘をもらえると大変喜びますので、宜しくお願い致します。