Edited at
NIJIBOXDay 21

BeautifulSoup4 であるページが参照している内部のimg,js,cssをピックアップする(+おまけ)


目的

http://example.com というサイトがあって、レスポンス内容に含まれている



  • imgタグで呼んでいる画像URL


  • scriptタグで呼んでいるスクリプトのURL


  • linkタグで呼んでいるスタイルシートのURL

をピックアップした後に、 同じホスト名から取るもの をリストアップするだけのもの。


コード

import requests

from bs4 import BeautifulSoup

def _fetch_targets(url: str) -> list:
"""指定したURLにアクセスした際に、直後に取る画像,JS,CSSファイルのURLリストを作る
"""

session = requests.Session()
resp = session.get(f"{url}/")
soup = BeautifulSoup(resp.content, 'html.parser')
# Image
img_tags = soup.find_all('img')
img_tags_origin = [
s.attrs['src'] for s in img_tags
if s.attrs['src'].startswith(url)
]
# JS
script_tags = [
s for s in soup.find_all('script')
if 'src' in s.attrs
]
script_tags_origin = [
s.attrs['src'] for s in script_tags
if s.attrs['src'].startswith(url)
]
# CSS
css_tags = [
s for s in soup.find_all('link')
if 'stylesheet' in s.attrs['rel']
]
css_tags_origin = [
s.attrs['href'] for s in css_tags
if s.attrs['href'].startswith(url)
]
# 一括加工
targets = img_tags_origin + script_tags_origin + css_tags_origin
return [t.replace(url, '') for t in targets]

# 使い方
if __name__ == '__main__':
_fetch_targets('http://example.com')


中身概要

基本的にはBeautifulSoup4の機能に乗っかり、「タグを探索→特定属性によるフィルタ→全部を束ねて整形」というシンプルな構成

imgタグは、「いくらなんでもsrcはあるだろう」と決めつける

scriptタグは、タグ内に直接書くことも可能なので、「src属性を持っていること」でフィルタリング

* linkタグは、いろいろと入り乱れているので、「rel属性がstylesheetであること」でフィルタリング


linkタグの属性について

CSSのものだけをlinkタグから抽出するときにrel属性を使うのだけれど、リストであることを初めて知った気がする。


本当の目的

Locust.ioを使ったサーバーへの負荷テストをしたくて、アクセス先だけでなくそのページが参照しているリソースも含めて処理したほうが負荷テストになるかなと思い実装してみた。


残りのコード


locustfile.py

from locust import HttpLocust, Task, TaskSet

# --------
# この辺に、さっきの_fetch_targetsが入る
# --------

def generate_get_task(task: Task, url: str) -> callable:
"""タスクジェネレータです
"""

def _get_task(task: Task):
task.client.get(url)
return _get_task

class TopPageWithResources(TaskSet):
"""テスト振る舞いの定義セット
"""

def __init__(self, parent):
"""アクセス対象のテストをまるごとセットする
"""

super().__init__(parent)
self.tasks = []
self.tasks.append(generate_get_task(self, '/'))
for t in _fetch_targets(self.locust.host):
self.tasks.append(generate_get_task(self, t, counter))

class WebsiteUser(HttpLocust):
"""テスト実行の実設定
"""

task_set = TopPageWithResources
min_wait = 500
max_wait = 900


動的に増やすときの正しい位置を調べるのが面倒がわからなかったので、

タスクを定義する関数を別途用意して、テスト対象 + _fetch_targetsで見つけたURL全てを、tasksに突っ込んでみた。


こんな感じになる

スクリーンショット 2018-12-18 16.59.20.png

トップページの構成が変わっても、そのままシナリオとして使えそうで、個人的には便利かなと思っている。