torinaブログ

DjangoとBootstrap4で作成したブログ
Python, Django, Kivy, Bootstrap, Apache等のメモです
ソースコード

python、urllib.parseを使ってURLを部品に切り分ける

Python Python標準ライブラリ
2016年10月22日11:04
http://docs.python.jp/3/library/urllib.parse.html

あるサイトをスクレイピングし、aタグのhref...
つまり、URLなんかをいくつか取得したとしましょう。例えば、以下のようなリストが取得できたとします。
from urllib import parse
urls = [
    'https://torina.top/quick/?quick-text=python',
    'https://torina.top/quick/?quick-text=django',
    'https://torina.top/quick/?quick-text=kivy',
    'https://torina.top/quick/?quick-text=bootstrap',
    'https://torina.top/quick/?quick-text=centos7',
]


これらのurlにまたアクセスし、タイトルや見出しをcsv等に出力したいとしましょう。
その際のファイル名はquick-textの値...つまり、「python.csv」や「django.csv」にしようと思います。

このような場合、urllib.parseを使うと非常に楽になります。
from urllib import parse
urls = [
    'https://torina.top/quick/?quick-text=python',
    'https://torina.top/quick/?quick-text=django',
    'https://torina.top/quick/?quick-text=kivy',
    'https://torina.top/quick/?quick-text=bootstrap',
    'https://torina.top/quick/?quick-text=centos7',
]

for url in urls:
    query = parse.urlparse(url).query
    quick_text = parse.parse_qs(query)["quick-text"][0]
    print(quick_text)


結果
python
django
kivy
bootstrap
centos7


もう少し段階を追ってみてみましょう。まずurlparseです。
from urllib import parse

url = 'https://torina.top/quick/?quick-text=python'
parse_result = parse.urlparse(url)
print(parse_result)


結果
ParseResult(scheme='https', netloc='torina.top', path='/quick/', params='', query='quick-text=python', fragment='')


urlparseは、URLを解析し、 6つの構成要素に分解します。
パッと見た感じで大体わかると思いますが、もう少し色々試してみましょう。
from urllib import parse

url = 'https://torina.top/quick?quick-text=python&a=1&b=1#footer'
parse_result = parse.urlparse(url)
print(parse_result)


ParseResult(scheme='https', netloc='torina.top', path='/quick', params='', query='quick-text=python&a=1&b=1', fragment='footer')


各属性には、単純にアクセス可能です。
from urllib import parse

url = 'https://torina.top/quick?quick-text=python&a=1&b=1#footer'
parse_result = parse.urlparse(url)
print(parse_result.query)
print(parse_result.path)
print(parse_result.scheme)


quick-text=python&a=1&b=1
/quick
https


本題である、quick-textの値を取りましょう。?や&でsplit()をしたりしても何とかなりそうですが、
parse_qsを使うと簡単です。
from urllib import parse

url = 'https://torina.top/quick?quick-text=python&a=1&b=1#footer'
parse_result = parse.urlparse(url)
query_dict = parse.parse_qs(parse_result.query)
print(query_dict)


辞書でかえってきますね。
{'quick-text': ['python'], 'b': ['1'], 'a': ['1']}


from urllib import parse

url = 'https://torina.top/quick?quick-text=python&a=1&b=1#footer'
parse_result = parse.urlparse(url)
query = parse.parse_qs(parse_result.query)
quick_text = query['quick-text'][0]
print(quick_text)


無事に取得できました!
python


今度はここから逆のことを行ってみましょう。つまり、辞書を文字列に変換し、他のschemeやpathをクエリ文字列を合体してURLを作成します。
まずは、辞書をクエリ文字列に変換します。
urlencode()を使います。
from urllib import parse

query_dict = {
    'quick-text': 'python',
    'a': 'b',
    'c': 'd',
}
query_string = parse.urlencode(query_dict)
print(query_string)


a=b&c=d&quick-text=python


urlparseを使いましたが、これの逆もちゃんとあります。urlunparseです。
引数には、urlparseで帰ってきたようなタプルを渡します。parts変数のような感じですね。
from urllib import parse

query_dict = {
    'quick-text': 'python',
    'a': 'b',
    'c': 'd',
}
query_string = parse.urlencode(query_dict)

parts = ('https', 'torina.top', '/quick', '', query_string, 'footer')
url = parse.urlunparse(parts)
print(url)


このように、元のURLになりますね!
単純な文字列操作でURLを作るよりも、柔軟性があります。ジェネリックなコードが書けることでしょう。
https://torina.top/quick?c=d&a=b&quick-text=python#footer


parse_qsは辞書を返し、辞書からurlencodeでクエリ文字列にできます。
なので、クエリ文字列をちょっと上書きしたり追加したいってときは、以下のようなこともできます。
from urllib import parse

url = 'https://torina.top/quick?quick-text=python&a=1&b=1#footer'
parse_result = parse.urlparse(url)
query_dict = parse.parse_qs(parse_result.query)
print(query_dict)

# クエリの辞書に追加したり更新したりする
add_dict = {
    'a': '2',
    'quick-text': 'Django'
}
query_dict.update(add_dict)
print(query_dict)


{'a': ['1'], 'quick-text': ['python'], 'b': ['1']}
{'a': '2', 'quick-text': 'Django', 'b': ['1']}


parse_qsを使うと、それぞれの値がリストになっています。これは何故か、というと以下のようなURLがあるからです。
from urllib import parse

query_string = 'quick-text=python&a=1&b=1&a=2'  # aが複数
print(query_string)

query_dict = parse.parse_qs(query_string)
print(query_dict)



quick-text=python&a=1&b=1&a=2
{'a': ['1', '2'], 'quick-text': ['python'], 'b': ['1']}


上のものをそのままurlencodeに渡すと少しよろしくないです。各値がリストなためですね。
from urllib import parse

query_string = 'quick-text=python&a=1&b=1&a=2'  # aが複数
print(query_string)

query_dict = parse.parse_qs(query_string)
print(query_dict)

query_string = parse.urlencode(query_dict)
print(query_string)


三つ目の出力があれになってますね。
quick-text=python&a=1&b=1&a=2
{'quick-text': ['python'], 'a': ['1', '2'], 'b': ['1']}
quick-text=%5B%27python%27%5D&a=%5B%271%27%2C+%272%27%5D&b=%5B%271%27%5D



この場合は、以下のようにしてしまうのをよく見ます。
parse_qsをparse_qslにし、それをdictで辞書に変換するのです。
from urllib import parse

query_string = 'quick-text=python&a=1&b=1&a=2'
print(query_string)

# 変更
query_dict = dict(parse.parse_qsl(query_string))
print(query_dict)

query_string = parse.urlencode(query_dict)
print(query_string)


すると、出力も大丈夫ですね。
quick-text=python&a=1&b=1&a=2
{'quick-text': 'python', 'a': '2', 'b': '1'}
quick-text=python&a=2&b=1



これらを使い、元のURLからクエリを抜き出し、追加と更新をし、またURLに戻してみましょう。
from urllib import parse

url = 'https://torina.top/quick?quick-text=python&a=1&b=1#footer'
parse_result = parse.urlparse(url)
query_dict = dict(parse.parse_qsl(parse_result.query))

# クエリの辞書に追加したり更新したりする
add_dict = {
    'a': '2',
    'quick-text': 'Django'
}
query_dict.update(add_dict)
query_string = parse.urlencode(query_dict)

parts = parse_result._replace(query=query_string)
new_url = parse.urlunparse(parts)
print(new_url)


OKですね。
https://torina.top/quick?b=1&quick-text=Django&a=2#footer



parts = parse_result._replace(query=query_string)について説明しましょう。
まず、urlparseで帰ってくるのは、タプルのサブクラスのインスタンスです。
変更不能なので、urlparseした結果と自分で変更したクエリ文字列を合体させるには、普通にやると手間です。
普通にやった例は...タプルをリストにして、4番目のクエリの部分を書き換えて、urlunparse...
parts = list(parse_result)
parts[4] = query_string
new_url = parse.urlunparse(parts)


しかし、_replaceというメソッドが準備されています。こいつで書き換える方が楽ちんです。
この_replaceは、名前付きタプルでも同様に使えます。
parts = parse_result._replace(query=query_string)
new_url = parse.urlunparse(parts)



urllib.parseには他にも色々と便利なものがあります。
urljoinなんかは、スクレイピングでよく使えます。
取得したリンク先が、絶対パスでも絶対パスでも動作するため、難しいことを考える必要がありません。
from urllib import parse

url = 'https://torina.top/main/305'
hrefs = [
    '305',
    '/serach/Python/',
    'https://torina.top/main/301'
]

for href in hrefs:
    new_url = parse.urljoin(url, href)
    print(new_url)



https://torina.top/main/305
https://torina.top/serach/Python/
https://torina.top/main/301