作ったもの
###( 入力 )
- 入力場所:Webブラウザ
- 入力内容:URLの__リクエストパラメータ "word"__に、__記事の検索単語__を渡したlocal hostのURL(port: 5000):str
http://127.0.0.1:5000/scraping?word=Qiita記事検索文字列
####例 :検索文字列として、"haskell"を入力
http://127.0.0.1:5000/scraping?word=haskell
###( 出力 )
####出力1:ブラウザへの出力
・__検索単語でヒットした、Qiita記事一覧画面(7ページ分)__からスクレイピングした、次の2つの項目
- __記事投稿者__を格納したList型オブジェクト:List[str]
- __記事タイトル__を格納したList型オブジェクト:List[str]
####出力2:カレント・ディレクトリへの出力
注:「カレント・ディレクトリ」とは、Pythonスクリプトファイルが格納されているディレクトリです
- 画像ファイル (拡張子: .png): Qiitaの記事事検索結果画面(7ページ分)の各画面のキャプチャ画像 (ファイル数:7ファイル)
- Excelファイル:記事の投稿者名、記事タイトル、記事URLなどが書き込まれたもの(ファイル数:1ファイル)
###操作手順
・Seleniumのwebdriver(chromedriver)が置かれたディレクトに移動し、__Terminalで以下を実行__する
$ python3 selenium_flask_qiita_multi_pages_get_data_while.py
・__Webブラウザ__を立ち上げて、__以下のURLにアクセス__する
http://127.0.0.1:5000/scraping?word=検索文字列
###(実行例)
ブラウザ画面に以下を入力
http://(127.0.0.1:5000/scraping?word=haskell
####出力1:Webブラウザ
また、裏側でSeleniumがアクセスした、Qiitaの記事検索結果画面の画面キャプチャ画像と、スクレイピング取得した情報が記録されたExcelファイルが出力される。
####出力2:Excelファイル(1件)と画像ファイル(7件)
( Excelファイル )
( 画像ファイル )
・(画面キャプチャ画像)1〜6ページ目の記事一覧ページ
( 掲載省略 )
###( 別解 ) 操作手順
・Seleniumのwebdriver(chromedriver)が置かれたディレクトに移動し、__Terminalで以下を実行__する
$ python3 selenium_flask_qiita_multi_pages_get_data_while.py
・ pip install httpie を実行した環境で、もう1つ、別のTerminalを立ち上げる
・立ち上げた新しいTerminalから、__httpコマンド__を用いてlocalhost (port 5000)にアクセスする
$ http 'http://127.0.0.1:5000/scraping/?word=検索文字列'
####( 別解の実行例 )
$ http 'http://127.0.0.1:5000/scraping/?word=haskell'
####出力1:上記を実行したTerminal
$ http 'http://127.0.0.1:5000/scraping?word=haskell'
HTTP/1.0 200 OK
Content-Length: 4091
Content-Type: text/html; charset=utf-8
Date: Wed, 11 Nov 2020 15:18:57 GMT
Server: Werkzeug/1.0.1 Python/3.9.0
{
"取得した記事の投稿者": [
"Tatsuki-I",
"mod_poppo",
"hiratara",
"Afo_guard_enthusiast",
"sgmryk",
"sparklingbaby",
"oskats1987",
"atsuyoshi-muta",
"dd0125",
"Izawa_",
"Izawa_",
"Tatsuki-I",
"takenobu-hs",
"satosystems",
"F_cy",
"sparklingbaby",
"a163236",
"pumbaacave",
"gogotanaka",
"nka0i",
"gogotanaka",
"ryo0ka",
"tgck",
"sahara",
"legokichi",
"arowM",
"tnoda_",
"yutasth",
"t-mochizuki",
"CPyRbJvCHlCs",
"TTsurutani",
"kanimum",
"Lugendre",
"eielh",
"reotasosan",
"busyoumono99",
"inatatsu_csg",
"hiroyuki_hon",
"Cj-bc",
"nakamurau1@github",
"gangun",
"makoraru",
"eielh",
"sk427",
"reotasosan",
"kitsukitsuki",
"ttatsf",
"airtoxin",
"hiruberuto",
"KenjiYamauchi",
"reotasosan",
"Tatsuki-I",
"reotasosan",
"ogata-k",
"takenobu-hs",
"junjihashimoto@github",
"hyone",
"lotz",
"nwtgck",
"gangun",
"reotasosan",
"sp4ghet",
"makoraru",
"yurucamp",
"kencoba",
"eielh",
"sk427",
"ttatsf",
"airtoxin",
"KenjiYamauchi"
],
"取得した記事タイトル": [
"HaskellでMD5 #Haskell",
"stylish-haskellをHexFloatLiteralsやNumericUnderscoresに対応させる",
"Haskell チュートリアル (Haskell Day 2016)",
"【Selenium】「次ページ」への繰返し移動は、while文を使う",
"VSCodeのHaskell拡張を使って最速開発環境構築",
"stylish-haskellをBlockArgumentsに対応させる",
"Haskell で Functor を使う",
"Windows10 HomeでDocker + VSCodeによるhaskellの環境構築",
"Docker + Haskell の Hello Worldビルド",
"HaskellのEitherについて",
"HaskellのEitherについて",
"Haskellの関数の型とかカリー化とか #Haskell",
"Haskell/GHC 記号の意味を検索するためのリファレンス集",
"Haskell と SQLite",
"Haskell入門記事備忘録",
"Haskell入門 - Stackのインストールと設定",
"Haskell 入門",
"Approaching Haskell",
"Ruby内にHaskellのコード埋め込むHaskellというGem作ったヨ!",
"Typing Haskell in Haskellを読んでみる",
"Ruby内にHaskellのコード埋め込むHaskellというGem作ったヨ!",
"SublimeREPLにstackのサポートを追加する",
"EmacsでHaskellプログラミングするための環境構築手順(haskell-mode.elの導入)",
"Haskellメモ",
"VisualStudioCodeでHaskell開発環境を整える",
"なぜHaskellを学ぶと良いか",
"Haskell インストールメモ",
"VSCodeでCouldn't start client Haskell IDEが出る(Windows 10)",
"Stackをhaskell-modeで使ってみよう",
"Haskellのお勉強その1-Haskell環境構築",
"Haskellの($)と(.)の違い",
"HaskellerがRustに入門してみた",
"Haskellの入門から中級者になるまでの指針",
"Atom EditorでHaskell",
"Haskellについてのアンケート",
"Atomエディタでide-haskellを使うまでの手順",
"Haskellで逆ポーランド記法の計算機を作る",
"MacでHaskellを触ってみるメモ 0.1",
"Haskellを始めてみた",
"ATOMのide-haskell導入手順(MacOS X)",
"HaskellでFizzBuzz",
"Haskellと層",
"IntelliJでHaskell",
"haskellでBayes",
"Haskellで食べていけるよ!!",
"Windows10でHaskell開発環境構築",
"Haskellで素数",
"Haskell入門",
"【翻訳】PureScriptとHaskellの違い +α",
"UbuntuでHaskell",
"Haskellについてのアンケート",
"Haskellerの1週間Rust入門チャレンジ Day 1 #Rust",
"4名のHaskeller先生方からの応援メッセージ",
"Haskellや周辺ツールについてのリンク集",
"Haskell GHC開発に関する情報源いろいろ",
"Inline-c:C++のネームスペースやテンプレートの対応について",
"hsenv を使って ghc のサンドボックス環境を作る",
"HerokuにHaskellのアプリを公開する",
"IntelliJでHaskellを実行する(haskell stackプロジェクト)",
"HaskellでFizzBuzz",
"Samuel Gélineau からの返信その1",
"Windows上のAtomで Haskell開発環境を整える。",
"Haskellと層",
"Haskellをすこしだけ紹介(フィボナッチ数列を書いてみる)",
"RustとHaskellのちょっとした違い",
"IntelliJでHaskell",
"haskellでBayes",
"Haskellで素数",
"Haskell入門",
"UbuntuでHaskell"
]
}
$
####出力2:Excelファイル(1件)と画像ファイル(7件)
#####( 掲載省略 )
##実装コード
# coding: utf-8
import time, json, argparse, datetime
from flask import Flask, abort, make_response
from flask_restful import Api, Resource, reqparse
from selenium import webdriver
from bs4 import BeautifulSoup
from pprint import pprint
import pandas as pd
import numpy as np
import time
app = Flask(__name__)
api = Api(app)
### 定数 ###
url = "http://qiita.com"
### 変数の初期化宣言 ###
pagenum = 0
num_of_search_pages = 7
class GetData(Resource):
def __init__(self, *args, **kwargs):
self.parser = reqparse.RequestParser()
self.parser.add_argument('word', type=str, default='Python')
super().__init__(*args, **kwargs)
def proceed_each_page(self, page_num, this_page_html, search_word, driver):
from bs4 import BeautifulSoup
this_soup = BeautifulSoup(this_page_html, 'lxml')
print("""
================================
{}ページ目を処理中...
===============================
""".format(page_num))
results = this_soup.find_all("h1", class_="searchResult_itemTitle")
# 結果を記事タイトルリストに格納
this_page_title_list = []
for result in results:
title_texts = result.findAll("a")
title_texts = str(title_texts[0]).replace("<em>", "").replace("</em>", "").split(">")[1:]
title_texts = title_texts[0]
pos = title_texts.find('</a')
title_text = title_texts[:pos]
this_page_title_list.append(title_text)
console_message = "検索結果画面の{}ページ目の記事件数: ".format(pagenum) + str(len(this_page_title_list)) + "\n\n"
pprint(this_page_title_list)
# 結果をURLリストに格納
this_page_url_list = []
for result in results:
href = result.findAll("a")[0].get("href")
this_page_url_list.append(str(url + href))
# 投稿者を投稿者リストに格納
# <div class="searchResult_header"><a href="/niiku-y">niiku-y</a>が2019/08/07に投稿</div>
this_page_author_list = []
results = this_soup.findAll(class_="searchResult_header")
for result in results:
author = result.findAll("a")[0].get("href")
author = author.replace("/", "")
this_page_author_list.append(author)
# nページ目から取得した記事であることをデータ保存
this_page_num_list = [page_num]*len(this_page_title_list)
## 検索結果画面のnページ目の画面スクリーンキャプチャを取得
# 画面の縦横サイズのデータを取得
w = driver.execute_script("return document.body.scrollWidth;")
h = driver.execute_script("return document.body.scrollHeight;")
driver.set_window_size(w,h)
# 画面スクリーンキャプチャファイル(画像ファイル)の保存場所とファイル名を指定
FILENAME = "./{word}_page{number}_screen_{datetime}.png".format(word=search_word, number=page_num, datetime=str(datetime.datetime.now()))
# 画像を保存
driver.save_screenshot(FILENAME)
#処理したWebページの情報を格納した各listを返す
return [this_page_num_list, this_page_author_list, this_page_title_list, this_page_url_list, driver]
@api.representation('application/json') #追加
def get(self):
args = self.parser.parse_args()
search_word = args['word']
print("\n\n入力された検索文字列: ", search_word, "\n")
print("記事一覧ページが複数ページに及ぶ場合、{}ページまでで処理を打ち切ります。".format(num_of_search_pages))
driver = webdriver.Chrome(executable_path='./chromedriver')
# Qiitaのトップページにアクセス
driver.get(url)
# 記事の検索ボックス欄に、キーワードを入力
search = driver.find_element_by_class_name("st-Header_searchInput")
search.send_keys(search_word)
search.submit()
time.sleep(5)
# 検索結果の記事一覧ページの1ページ目のHTMLを取得
first_page_html = driver.page_source.encode('utf-8')
# 1ページ目を処理
page_num = 1
all_page_num_list = []
all_page_author_list = []
all_page_title_list = []
all_page_url_list = []
this_page_num_list, this_page_author_list, this_page_title_list, this_page_url_list, driver = self.proceed_each_page(page_num, first_page_html, search_word, driver)
all_page_num_list = all_page_num_list + this_page_num_list
all_page_author_list = all_page_author_list + this_page_author_list
all_page_title_list = all_page_title_list + this_page_title_list
all_page_url_list = all_page_url_list + this_page_url_list
# 受け取ったdriverの指示対象のWebページに、次のページがある場合は、次のページに移動する
# next_page_urlの返り値はlist型。次のページが記載された上記のタグが存在しない場合は、空のlistが返る
next_page_url = driver.find_element_by_class_name("js-next-page-link").get_attribute("href")
# 2ページ目から(最終ページ目 もしくは、{num_of_search_pages}ページ目までのいずれか小さいページ番号目)までをループ処理する
# (num_of_search_pages)ページ目を最終ページにするためには、{num_of_search_pages -1)回、次のページをめくる
while len(next_page_url) > 0 and page_num <= (num_of_search_pages - 1):
driver.get(next_page_url)
#要素がロードされるまでの待ち時間を10秒に設定
driver.implicitly_wait(10)
#time.sleep(5)
next_page_html = driver.page_source.encode('utf-8')
page_num += 1
this_page_num_list, this_page_author_list, this_page_title_list, this_page_url_list, driver = self.proceed_each_page(page_num, next_page_html, search_word, driver)
all_page_num_list = all_page_num_list + this_page_num_list
all_page_author_list = all_page_author_list + this_page_author_list
all_page_title_list = all_page_title_list + this_page_title_list
all_page_url_list = all_page_url_list + this_page_url_list
next_page_url = driver.find_element_by_class_name("js-next-page-link").get_attribute("href")
print("=======")
print("次のページのURL : " + str(next_page_url))
else:
print("\n\n記事検索結果の最後のページの処理が終わりました。\n\n")
# Excelファイル出力
message = "取得した検索結果のデータをExcelファイルに出力します"
print("\n\n" + message + "\n\n")
data = np.array([all_page_num_list, all_page_author_list, all_page_title_list, all_page_url_list]).T
index_list = list(range(len(all_page_num_list)))
column_list = ['検索結果画面内の掲載ページ番号', '投稿者', '記事のタイトル', '記事のURL']
output_df = pd.DataFrame(data, columns=column_list, index=index_list)
print("\n\n取得したデータ\n")
pprint(output_df)
# outputファイル名
output_file_name = str(datetime.datetime.now()) + "_Search: " + search_word
# 結果をExcelファイルに出力
output_df.to_excel('./'+output_file_name + '.xlsx', sheet_name='Qiita_Articles_list')
# Webページに自動アクセスするために生成したdriverインスタンスを閉じて消去(メモリ開放)する
time.sleep(5)
driver.close()
driver.quit()
#return output_df
api_response = {"取得した記事タイトル" : all_page_title_list,
"取得した記事の投稿者" : all_page_author_list}
response = make_response(json.dumps(api_response, ensure_ascii=False))
return response
### メソッド定義おわり
api.add_resource(GetData, '/scraping')
if __name__ == "__main__":
app.run(debug=False)
##( 参考にしたwebサイト )