def analysis_url(self, divs: List[webdriver.remote.webelement.WebElement], target_author: str) -> List[BookInfo]: """ 検索結果解析 検索結果から以下を解析 本のタイトル 発売日 著者名 価格 商品ページへのURL [I] html_info : requets.get結果 [O] BookInfo : 解析結果 """ Debug.tmpprint("func : analysis_url") book_infos: List[BookInfo] = [] # 情報解析/取得 for div in divs: book_info = BookInfo() # 著者名 book_info.author = self.__get_book_author(div) # TODO:著者名が検索対象と一致しなければとばす # マッチ条件どうするか #if book_info.author != target_author: # continue # タイトル book_info.title = self.__get_book_title(div) # 発売日 book_info.date = self.__get_book_date(div) # 価格 book_info.price = self.__get_book_price(div) # 商品URL book_info.url = self.__get_book_url(div, book_info.title) # 結果保持 book_infos.append(book_info) return book_infos
def __create_url_for_csv(self, filename: str) -> None: """ 検索データ情報リスト生成(csvから生成) 著者名リストを生成.別途著者名をキーとしたハッシュマップを生成し,データとして検索開始日を保持する [I] filename : 確認対象ファイル名 """ Debug.tmpprint("func : create_url_for_csv") encode_str = "utf-8" csv_type = CsvUtil.is_utf8_file_with_bom(filename) if csv_type is CsvEncodeType.UTF_8_BOM: encode_str = "utf-8-sig" elif csv_type is CsvEncodeType.SHIFT_JIS: encode_str = "shift_jis" else: encode_str = "utf-8" serach_list_dict = {} # csv読込み with open(filename, mode="r", encoding=encode_str, newline="") as csvfile: reader = csv.reader(csvfile) next(reader) # ヘッダ読み飛ばし # データ生成 for row in reader: # dictionaryに著者名をキーとしてデータを保持 if row[1] == "": row[1] = datetime.datetime.today().strftime("%Y/%m/%d") # 期間が空白なら,実行日を設定 serach_list_dict[row[0]] = row[1] # 結果を保持 self.search_infos = serach_list_dict
def create_search_info_list(self, filename:str) -> None: """ 検索データ情報リストの生成 著者名リストを生成.別途著者名をキーとしたハッシュマップを生成し,データとして検索開始日を保持する [I] filename : 確認対象ファイル名 [O] dict : キー=著者名,データ=検索開始日 """ Debug.tmpprint("func : create_search_info_list") if filename.find(".csv") > 0: self.__create_url_for_csv(filename) else: self.__create_url_for_db()
def main(): Debug.tmpprint("Start\n") # ファイルチェック # ファイルが見つからなかった場合は,新規にファイルを作成し,終了する #if check_search_file(READ_FILE_NAME) == False: # print("finish!") # return # クローリング用クラス生成 book_crawling = BookInfoCrawling() # DB設定 #if book_crawling.set_table_key("nyasai") is False: # print("finish!") # return # 検索データ取得 book_crawling.create_search_info_list(READ_FILE_NAME) # 著者リスト生成 book_crawling.create_author_list() # 著者リスト保持 target_author_list = book_crawling.get_author_list() # url生成 url_list = book_crawling.create_url() # 検索 all_book_infos: List[BookInfo] = [] book_scraping = BookInfoScraping() search_cnt = 0 for url in url_list: # 検索結果取得 Debug.tmpprint(url) search_result_divs = book_crawling.exec_search(url) # 正常に結果が取得できた場合 if len(search_result_divs) != 0: # 解析実行 one_author_book_info = book_scraping.analysis_url( search_result_divs, target_author_list[search_cnt]) # 結果をリストに保持 all_book_infos.append(one_author_book_info) # 結果出力 #output_result(one_author_book_info, book_crawling.get_author_list()[search_cnt], book_crawling.get_serch_info()[book_crawling.get_author_list()[search_cnt]]) search_cnt += 1 # ドライバクローズ book_crawling.cloase_driver() # 結果出力 # TODO: 既に一度出力していれば無視するか?それとも毎回全上書きを行うか? # 既にファイルが有れば,そのファイルとの差分をとって結果を何かしらで通知. # ファイルがなければ全て通知 output_result_for_csv(all_book_infos, book_crawling.get_author_list(), book_crawling.get_serch_info()) output_result_for_html(all_book_infos, book_crawling.get_author_list(), book_crawling.get_serch_info()) print("finish!")
def create_url(self) -> List[str]: """ URL生成 著者名から検索用URLを生成する [I] name_data : 著者名リスト [O] list : URLリスト """ Debug.tmpprint("func : create_url") url_list = [] # 全key名でURLを生成し,listに保持 for search_name in self.__author_list: Debug.tmpprint(search_name) url_list.append(AMAZON_SEARCH_URL + urllib.parse.quote(search_name.encode("utf-8")) + AMAZON_SEARCH_URL2) # 日本語を16進数に変換 return url_list
def check_search_file(filename: str) -> bool: """ 検索リストファイル確認 ファイルが存在しなければ,新たに生成する [I] filename : 確認対象ファイル名 """ Debug.tmpprint("func : check_search_file") if os.path.isfile(filename) == True: return True else: print("検索対象リストが見つかりません.新規に生成します.") # ファイル生成 strs = "著者名(名字と名前の間は空白を入れないこと),取得開始期間(空なら実行日を開始日として取得)\n" with open(filename, mode="w", encoding="utf-8-sig", newline="") as csvfile: csvfile.write(strs) return False
def __create_url_for_db(self) -> None: """ 検索データ情報リスト生成(DBから生成) 著者名リストを生成.別途著者名をキーとしたハッシュマップを生成し,データとして検索開始日を保持する [I] filename : 確認対象ファイル名 """ Debug.tmpprint("func : __create_url_for_db") serach_list_dict = {} # DB読込み serach_info = self.__db_ctrl.get_db_search_list() # データ生成 for row in serach_info: # dictionaryに著者名をキーとしてデータを保持 if row.search_date is None: row.search_date = datetime.datetime.today().strftime("%Y/%m/%d") # 期間が空白なら,実行日を設定 serach_list_dict[row.author_name] = row.search_date # 結果を保持 self.search_infos = serach_list_dict
def add_book_info(self, author: str, date: str): """ 検索対象情報追加 [I] author : 著者名 [I] date 出力対象日閾値 """ # 接続 self.__connect_db() # 追加 try: query_str = "insert into " + self.__book_info_tbl_name + " values (?, ?)" self.__book_info_tbl_cursor.execute(query_str, (author, date)) except sqlite3.Error as e: Debug.dprint(e.args[0]) # output for row in self.__book_info_tbl_cursor.execute( "select * from %s" % self.__book_info_tbl_name): Debug.tmpprint(row) # 接続解除 self.__disconnect_db()
def output_result_for_csv(all_book_infos: List[List[BookInfo]], author_list: List[str], output_date: dict): """ CSV書き込み 著者名と本リスト,URL,発売日,価格を保存する [I] all_book_infos : 解析後の本情報(全著者分),author_list : 著者名リスト,output_date : 出力対象の日付 """ Debug.tmpprint("func : output_result_for_csv") output_filename = "new_book_info_" + datetime.datetime.now().strftime( "%Y%m%d%H%M%S") + ".csv" #csvオープン with open(output_filename, mode="a", newline="", encoding='utf-8-sig') as csvfile: csvfile.write("著者名,タイトル,発売日,価格,商品URL\n") # 検索対象データ分ループ for author_cnt in range(0, len(author_list), 1): book_infos = all_book_infos[author_cnt] # 1検索対象データの結果リストから,著者名が一致するもののみを取得 book_info_cnt = 0 for book_info in book_infos: try: if book_info.author.find(author_list[author_cnt]) != -1: # 期間が指定日以降なら保存する if datetime.datetime.strptime(book_info.date, "%Y/%m/%d") \ >= datetime.datetime.strptime(output_date[author_list[author_cnt]],"%Y/%m/%d"): output_str = "" # 著者名 output_str += book_info.author + "," # タイトル output_str += book_info.title + "," # 発売日 output_str += book_info.date + "," # 価格 output_str += "\"" + book_info.price + "\"," # 商品URL output_str += book_info.url + "\n" csvfile.write(output_str) except IndexError: print("IndexError!! -> " + book_info.author) book_info_cnt -= 1 continue book_info_cnt += 1
def set_user_info_key(self, user_name: str) -> bool: """ ユーザ情報テーブル主キー設定 [I] username ユーザ名 [O] 結果 """ result = False try: # DB接続 self.__connect_db() # 検索 query_str = "select * from " + USER_INFO + " where user_name=?" self.__user_info_tbl_cursor.execute(query_str, (user_name, )) if self.__user_info_tbl_cursor.fetchone is not None: # 検索対象情報テーブル名保持 self.__book_info_tbl_name = user_name + BOOK_INFO result = True except sqlite3.Error as e: Debug.dprint(e.args[0]) # 未登録情報 self.__disconnect_db() return result
def __get_book_price(self, div: webdriver.remote.webelement.WebElement) -> str: """ 本の価格取得 [I] div : htmlデータ [O] list : 本の価格リスト """ Debug.tmpprint("func : get_book_price") # 価格部分抽出 html_element = lxml.html.fromstring(div.get_attribute('innerHTML')) prices = html_element.xpath( "//div[contains(@class, 'sg-row')]"\ "//div[contains(@class, 'sg-col-inner')]"\ "//div[contains(@class, 'a-row')]"\ "//span[contains(@class, 'a-price')]" "//span[contains(@class, 'a-offscreen')]") formatting_author_str: str = "" for price in prices: formatting_author_str = price.text_content().encode( "utf-8").decode("utf-8") Debug.tmpprint(formatting_author_str) break return formatting_author_str
def get_db_search_list(self) -> list: """ 著者名リスト取得 [O] 著者リスト(DBAuthorInfo型) """ search_list = [] try: # DB接続 self.__connect_db() # 全データ取得 query_str = "select * from " + self.__book_info_tbl_name self.__book_info_tbl_cursor.execute(query_str) # 1データずつリストに保持 for row in self.__book_info_tbl_cursor.fetchall(): book_info = DBAuthorInfo() book_info.author_name = row[0] book_info.search_date = row[1] search_list.append(book_info) except sqlite3.Error as e: Debug.dprint(e.args[0]) # 接続解除 self.__disconnect_db() return search_list
def output_result_for_html(all_book_infos: List[List[BookInfo]], author_list: List[str], output_date: dict): """ HTML書き込み 著者名と本リスト,URL,発売日,価格を保存する [I] all_book_infos : 解析後の本情報(全著者分),author_list : 著者名リスト,output_date : 出力対象の日付 """ Debug.tmpprint("func : output_result_for_html") output_filename = "new_book_info_" + datetime.datetime.now().strftime( "%Y%m%d%H%M%S") + ".html" #csvオープン with open(output_filename, mode="a", newline="") as htmlfile: # 検索対象データ分ループ for author_cnt in range(0, len(author_list), 1): book_infos = all_book_infos[author_cnt] # 1検索対象データの結果リストから,著者名が一致するもののみを取得 book_info_cnt = 0 for book_info in book_infos: try: if book_info.author.find(author_list[author_cnt]) != -1: # 期間が指定日以降なら保存する if datetime.datetime.strptime(book_info.date,"%Y/%m/%d") \ >= datetime.datetime.strptime(output_date[author_list[author_cnt]],"%Y/%m/%d"): output_str = "" # 著者 output_str += book_info.author + "<br/>" # タイトル output_str += book_info.title + "<br/>" # 発売日 output_str += book_info.date + "<br/>" # 価格 output_str += book_info.price + "<br/>" # 商品URL output_str += '<a href="' + book_info.url + 'target="_blank"' + '">Link</a><br/><br/>' htmlfile.write(output_str) except IndexError: print("IndexError!! -> " + book_info.author) book_info_cnt -= 1 continue book_info_cnt += 1
def __get_book_date(self, div: webdriver.remote.webelement.WebElement) -> str: """ 本の発売日取得 [I] div : htmlデータ [O] list : 本の発売日リスト """ Debug.tmpprint("func : get_book_date") # 発売日部分取得 html_element = lxml.html.fromstring(div.get_attribute('innerHTML')) dates = html_element.xpath( "//div[contains(@class, 'sg-row')]"\ "//div[contains(@class, 'sg-col-inner')]"\ "//div[contains(@class, 'a-section a-spacing-none')]"\ "//div[contains(@class, 'a-row a-size-base a-color-secondary')]"\ "//span[contains(@class, 'a-size-base a-color-secondary a-text-normal')]") formatting_title_str: str = "" for date in dates: formatting_title_str = datetime.datetime.strptime( date.text_content().encode("utf-8").decode("utf-8"), "%Y/%m/%d").strftime("%Y/%m/%d") Debug.tmpprint(formatting_title_str) break return formatting_title_str
def __get_book_url(self, div: webdriver.remote.webelement.WebElement, title_info: list) -> str: """ 本の商品ページURL取得 [I] div : htmlデータ [O] list : 本の商品ページリスト """ Debug.tmpprint("func : get_book_url") # URL部分抽出 html_element = lxml.html.fromstring(div.get_attribute('innerHTML')) urls = html_element.xpath( "//div[contains(@class, 'sg-row')]"\ "//div[contains(@class, 'sg-col-inner')]"\ "//div[contains(@class, 'a-section a-spacing-none')]"\ "//h2[contains(@class, 'a-size-mini a-spacing-none a-color-base s-line-clamp-2')]"\ "//a[contains(@class, 'a-link-normal a-text-normal')]"\ "//@href") LINK_URL = "https://www.amazon.co.jp" result_url_str = "" for url in urls: result_url_str = LINK_URL + url Debug.tmpprint(result_url_str) break return result_url_str
def __get_book_title(self, div: webdriver.remote.webelement.WebElement) -> str: """ 本のタイトル取得 [I] div : htmlデータ [O] list : 本のタイトルリスト """ Debug.tmpprint("func : get_book_title") # 商品タイトル部分抽出 html_element = lxml.html.fromstring(div.get_attribute('innerHTML')) titles = html_element.xpath( "//div[contains(@class, 'sg-row')]" "//div[contains(@class, 'sg-col-inner')]" "//div[contains(@class, 'a-section a-spacing-none')]" "//h2[contains(@class, 'a-size-mini a-spacing-none a-color-base s-line-clamp-2')]" "//span[contains(@class, 'a-size-medium a-color-base a-text-normal')]" ) formatting_title_str: str = "" for title in titles: formatting_title_str = title.text_content().encode("utf-8").decode( "utf-8") Debug.tmpprint(formatting_title_str) break return formatting_title_str
def __get_book_author(self, div: webdriver.remote.webelement.WebElement) -> str: """ 本の著者名取得 [I] div : htmlデータ [O] list : 本の著者名リスト """ Debug.tmpprint("func : get_book_author") # 著者名部分抽出 html_element = lxml.html.fromstring(div.get_attribute('innerHTML')) authors_link = html_element.xpath( "//div[contains(@class, 'sg-row')]"\ "//div[contains(@class, 'sg-col-inner')]"\ "//div[contains(@class, 'a-section a-spacing-none')]"\ "//div[contains(@class, 'a-row a-size-base a-color-secondary')]"\ "//a[contains(@class, 'a-size-base')]") authors_nonlink = html_element.xpath( "//div[contains(@class, 'sg-row')]" "//div[contains(@class, 'sg-col-inner')]" "//div[contains(@class, 'a-section a-spacing-none')]" "//div[contains(@class, 'a-row a-size-base a-color-secondary')]" "//span[contains(@class, 'a-size-base')]") formatting_author_str: str = "" is_continue = lambda target_str: target_str == "" or target_str == "、 " or target_str == ", " is_break = lambda target_str: target_str == " | " for author in authors_link: tmp_str = author.text_content().encode("utf-8").decode("utf-8") # 無効,区切りは無視 if is_continue(tmp_str): continue # 日付との区切り部分のため終了 if is_break(tmp_str): break # 改行と空白を削除 tmp_str = tmp_str.replace("\n", "") tmp_str = tmp_str.replace(" ", "") formatting_author_str = tmp_str Debug.tmpprint(formatting_author_str) for author in authors_nonlink: tmp_str = author.text_content().encode("utf-8").decode("utf-8") # 無効,区切りは無視 if is_continue(tmp_str): continue # 日付との区切り部分のため終了 if is_break(tmp_str): break # 改行と空白を削除 tmp_str = tmp_str.replace("\n", "") tmp_str = tmp_str.replace(" ", "") # 現状保持無しなら新規追加 if len(formatting_author_str) == 0: formatting_author_str = tmp_str else: # 保持していれば文字列追加 formatting_author_str += ("、" + tmp_str) Debug.tmpprint(formatting_author_str) return formatting_author_str
def create_db(self, user_name: str) -> bool: """ DB生成 """ try: # 接続 self.__connect_db() # テーブル生成 # 未生成時のみ生成を行う #self.__user_info_tbl_cursor.execute("drop table if exists %s" % USER_INFO) # ユーザ情報テーブル self.__user_info_tbl_cursor.execute( "create table if not exists %s (user_name text primary key, table_name text)" % USER_INFO) # self.__book_info_tbl_name = user_name + BOOK_INFO #self.__book_info_tbl_cursor.execute("drop table if exists %s" % (table_name)) self.__book_info_tbl_cursor.execute( "create table if not exists %s (author_name text primary key, date text)" % (self.__book_info_tbl_name)) # 検索情報テーブルを追加 query_str = "insert into " + USER_INFO + " values (?, ?)" # 現データ数取得 #self.__user_info_tbl_cursor.execute("select count(*) from %s " % self.__user_info_tbl_name) #data_max = self.__user_info_tbl_cursor.fetchall() self.__user_info_tbl_cursor.execute( query_str, (user_name, self.__book_info_tbl_name)) except sqlite3.Error as e: # 検索対象情報テーブル名保持 self.__book_info_tbl_name = user_name + BOOK_INFO Debug.dprint(e.args[0]) return False # output for row in self.__user_info_tbl_cursor.execute("select * from %s" % USER_INFO): Debug.tmpprint(row) for row_sub in self.__book_info_tbl_cursor.execute( "select * from %s" % row[1]): Debug.tmpprint(row_sub) # 接続解除 self.__disconnect_db() return True
def exec_search(self, url:str) -> List[webdriver.remote.webelement.WebElement]: """ 検索実行 渡されたURLから検索処理を実行する [I] url : 検索実行URL [O] 検索結果 """ Debug.tmpprint("func : exec_search") self.__driver.get(url) body = self.__driver.find_element_by_tag_name("body") body.send_keys(Keys.CONTROL + 't') divs = self.__driver.find_elements_by_class_name('s-result-item') # 失敗時は一定時間待ってから再度取得を試みる if len(divs) == 0: is_ok = False for retry in range(0,REQUEST_RETRY_NUM,1): time.sleep(REQUEST_WAIT_TIME *( retry+1)) # 再度実行 divs = self.__driver.find_elements_by_class_name('s-result-item') if len(divs) != 0: is_ok = True break else: if retry == REQUEST_RETRY_NUM: # リトライ上限に達した Debug.dprint("request err -> " + self.__author_list[self.__search_cnt]) if is_ok == False: # 最後まで成功しなかった場合該当データ削除 self.__search_infos.pop(self.__author_list[self.__search_cnt]) self.__author_list.pop(self.__search_cnt) # 結果なし divs = [] # 検索ログ出力 Debug.dprint("search author(" + str(self.__search_cnt + 1) + "/" + str(self.get_author_list_num()) + ") -> " + self.__author_list[self.__search_cnt]) # 検索数を進める self.__search_cnt += 1 # 結果を返す return divs