banner
cos

cos

愿热情永存,愿热爱不灭,愿生活无憾
github
tg_channel
bilibili

pythonネットワーククローラー学習ノート(1) 簡単な静的ウェブページのクロール

一、urllib3 を使用して HTTP リクエストを実現#

1. リクエストの生成#

  • request メソッドを使用してリクエストを生成します。プロトタイプは以下の通りです。

urllib3.request(method,url,fields=None,headers=None,**urlopen_kw)

パラメータ説明
methodstring を受け取ります。リクエストの種類を示します。例えば、"GET"(通常使用)、"HEAD"、"DELETE" など、デフォルト値はありません。
urlstring を受け取ります。文字列形式の URL を示します。デフォルト値はありません。
fieldsdict を受け取ります。リクエストタイプに関連するパラメータを示します。デフォルトは None です。
headersdict を受け取ります。リクエストヘッダーに関連するパラメータを示します。デフォルトは None です。
**urlopen_kwdict および Python のデータ型のデータを受け取ります。具体的な要件やリクエストの種類に応じて追加できるパラメータで、通常は辞書型または具体的なデータに設定されます。
code:
import urllib3
http = urllib3.PoolManager()
rq = http.request('GET',url='http://www.pythonscraping.com/pages/page3.html')
print('サーバー応答コード:', rq.status)
print('応答内容:', rq.data)

2. リクエストヘッダーの処理#

headers パラメータを渡すことで、辞書型を定義して実現できます。User-Agent 情報を含む辞書を定義し、ブラウザとして Firefox と Chrome を使用し、オペレーティングシステムは "Window NT 6.1;Win64; x64" とし、サイト "http://www.tipdm/index.html" に headers パラメータを持つ GET リクエストを送信します。headers パラメータは定義した User-Agent 辞書です。

import urllib3
http = urllib3.PoolManager()
head = {'User-Agent':'Window NT 6.1;Win64; x64'}
http.request('GET',url='http://www.pythonscraping.com/pages/page3.html',headers=head)

3. タイムアウト設定#

ネットワークの不安定などの理由でパケットが失われるのを防ぐために、リクエストに timeout パラメータを追加して設定できます。通常は浮動小数点数で、URL の後にこのリクエストのすべてのパラメータを直接設定することも、接続と読み取りの timeout パラメータを個別に設定することもできます。PoolManager インスタンスで timeout パラメータを設定すると、そのインスタンスのすべてのリクエストに適用されます。

直接設定

http.request('GET',url='',headers=head,timeout=3.0)
# 3秒を超えるとタイムアウトします
http.request('GET',url='http://www.pythonscraping.com/pages/page3.html',headers=head,timeout=urllib3.Timeout(connect=1.0,read=2.0))
# 接続が1秒を超え、読み取りが2秒を超えるとタイムアウトします

インスタンスのすべてのリクエストに適用

import urllib3
http = urllib3.PoolManager(timeout=4.0)
head = {'User-Agent':'Window NT 6.1;Win64; x64'}
http.request('GET',url='http://www.pythonscraping.com/pages/page3.html',headers=head)
# 4秒を超えるとタイムアウトします

4. リクエスト再試行設定#

urllib3 ライブラリは、retries パラメータを設定することで再試行を制御できます。デフォルトでは 3 回のリクエスト再試行と 3 回のリダイレクトが行われます。再試行回数をカスタマイズするには、retries パラメータに整数を設定します。retries インスタンスを定義することで、リクエストの再試行回数とリダイレクト回数をカスタマイズできます。リクエストの再試行とリダイレクトを同時に無効にするには、retries パラメータに False を設定します。リダイレクトのみを無効にするには、redirect パラメータに False を設定します。タイムアウト設定と同様に、PoolManager インスタンスで retries パラメータを設定して、すべてのリクエストの再試行戦略を制御できます。

インスタンスのすべてのリクエストに適用

import urllib3
http = urllib3.PoolManager(timeout=4.0,retries=10)
head = {'User-Agent':'Window NT 6.1;Win64; x64'}
http.request('GET',url='http://www.pythonscraping.com/pages/page3.html',headers=head)
# 4秒を超えるとタイムアウトし、10回再試行します

5. 完全な HTTP リクエストの生成#

urllib3 ライブラリを使用して、http://www.pythonscraping.com/pages/page3.html に完全なリクエストを生成します。このリクエストには、リンク、リクエストヘッダー、タイムアウト時間、再試行回数の設定が含まれている必要があります。
ここに画像の説明を挿入
ここに画像の説明を挿入
エンコーディング方式は utf-8 に注意してください。

import urllib3
# リクエスト送信のインスタンス
http = urllib3.PoolManager()
# URL
url = 'http://www.pythonscraping.com/pages/page3.html'
# リクエストヘッダー
head = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.56'}
# タイムアウト時間
tm = urllib3.Timeout(connect=1.0,read=3.0)
# 再試行回数とリダイレクト回数の設定、リクエストを生成
rq = http.request('GET',url=url,headers=head,timeout=tm,redirect=4)
print('サーバー応答コード:', rq.status)
print('応答内容:', rq.data.decode('utf-8'))
サーバー応答コード: 200
応答内容: <html>
<head>
<style>
img{
	width:75px;
}
table{
	width:50%;
}
td{
	margin:10px;
	padding:10px;
}
.wrapper{
	width:800px;
}
.excitingNote{
	font-style:italic;
	font-weight:bold;
}
</style>
</head>
<body>
<div id="wrapper">
<img src="../img/gifts/logo.jpg" style="float:left;">
<h1>完全に普通のギフト</h1>
<div id="content">ここには、あなたの友達がきっと気に入る完全に普通で、完全に合理的なギフトのコレクションがあります!私たちのコレクションは
高給取りのフリーランスのチベット僧によって手作りされています。<p>
オンラインショッピングカートの作り方はまだわかりませんが、以下の住所に小切手を送っていただければ、<br>
123 Main St.<br>
アブジャ、ナイジェリア
</br>その後、あなたの完全に素晴らしいギフトをすぐにお送りします!ギフトラッピングには追加で$5.00を含めてください。</div>
<table id="giftList">
<tr><th>
アイテムタイトル
</th><th>
説明
</th><th>
コスト
</th><th>
画像
</th></tr>

<tr id="gift1" class="gift"><td>
野菜バスケット
</td><td>
この野菜バスケットは、健康を気にする(または太り気味の)友達に最適なギフトです!
<span class="excitingNote">今、超カラフルなピーマン付き!</span>
</td><td>
$15.00
</td><td>
<img src="../img/gifts/img1.jpg">
</td></tr>

<tr id="gift2" class="gift"><td>
ロシアの入れ子人形
</td><td>
訓練された猿によって手描きされたこれらの素晴らしい人形は、値段がつけられないほどです!そして「値段がつけられない」とは「非常に高価」という意味です! <span class="excitingNote">セットごとに8体の人形!プレゼントが8倍に!</span>
</td><td>
$10,000.52
</td><td>
<img src="../img/gifts/img2.jpg">
</td></tr>

<tr id="gift3" class="gift"><td>
魚の絵
</td><td>
この絵に何か怪しいことがあると感じたら、それは魚だからです! <span class="excitingNote">訓練された猿によって手描きされています!</span>
</td><td>
$10,005.00
</td><td>
<img src="../img/gifts/img3.jpg">
</td></tr>

<tr id="gift4" class="gift"><td>
死んだオウム
</td><td>
これは元オウムです! <span class="excitingNote">それとも、ただ休んでいるだけ?</span>
</td><td>
$0.50
</td><td>
<img src="../img/gifts/img4.jpg">
</td></tr>

<tr id="gift5" class="gift"><td>
ミステリーボックス
</td><td>
サプライズが好きなら、このミステリーボックスはあなたのためのものです!明るい色の表面に置かないでください。油染みを引き起こす可能性があります。 <span class="excitingNote">友達を驚かせ続けましょう!</span>
</td><td>
$1.50
</td><td>
<img src="../img/gifts/img6.jpg">
</td></tr>
</table>
</p>
<div id="footer">
&copy; 完全に普通のギフト社 <br>
+234 (617) 863-0736
</div>

</div>
</body>
</html>

二、requests ライブラリを使用して HTTP リクエストを実現#

import requests
url = 'http://www.pythonscraping.com/pages/page3.html'
rq2 = requests.get(url)
rq2.encoding = 'utf-8'
print('応答コード:',rq2.status_code)
print('エンコーディング:',rq2.encoding)
print('リクエストヘッダー:',rq2.headers)
print('内容:',rq2.text)

文字エンコーディングの問題を解決#

注意が必要なのは、requests ライブラリが誤って推測した場合、手動で encoding を指定する必要があり、返されたウェブページの内容解析に文字化けが発生するのを避けることです。手動指定の方法は柔軟性がなく、クローリング中の異なるウェブページのエンコーディングに適応できませんが、chardet ライブラリを使用すると比較的簡単で柔軟です。chardet ライブラリは、非常に優れた文字列 / ファイルエンコーディング検出モジュールです。
chardet ライブラリは detect メソッドを使用して、与えられた文字列のエンコーディングを検出します。detect メソッドの一般的なパラメータとその説明は以下の通りです。

パラメータ説明
byte_strstring を受け取ります。エンコーディングを検出する必要がある文字列を示します。デフォルト値はありません。
import chardet
chardet.detect(rq2.content)

出力:100% の確率で ascii コードでエンコードされています。
ここに画像の説明を挿入
完全なコード

import requests
import chardet
url = 'http://www.pythonscraping.com/pages/page3.html'
head={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.56'}
rq2 = requests.get(url,headers=head,timeout=2.0)
rq2.encoding = chardet.detect(rq2.content)['encoding']
print('内容:',rq2.content)

三、ウェブページの解析#

Chrome 開発者ツールの各パネルの機能は以下の通りです。
ここに画像の説明を挿入

1. 要素パネル#

クローラー開発において、要素パネルは主にページ要素の位置を確認するために使用されます。例えば、画像の位置やテキストリンクの位置などです。パネルの左側には現在のページの構造がツリー状に表示されており、三角形のシンボルをクリックするとブランチが展開されます。
ここに画像の説明を挿入

2. ソースコードパネル#

ソースコードパネル(Sources)に切り替えます。左側の「tipdm」フォルダ内の「index.html」ファイルをクリックすると、中央にその完全なコードが表示されます。

3. ネットワーパネル#

ネットワーパネル(Network)に切り替え、ページを再読み込みする必要があります。特定のリソースをクリックすると、そのリソースのヘッダー情報、プレビュー、応答情報、Cookies、費やした時間の詳細が中央に表示されます。ここに画像の説明を挿入

四、正規表現を使用してウェブページを解析#

1. Python 正規表現:文字列内の名前と電話番号を探す#

正規表現は、パターンマッチングや置換に使用できるツールであり、ユーザーが一連の特殊文字を使用してマッチングパターンを構築し、そのパターンを比較対象の文字列やファイルと比較することができます。比較対象にマッチングパターンが含まれているかどうかに応じて、対応するプログラムを実行します。

rawdata = “555-1239Moe Szyslak(636) 555-0113Burns, C.Montgomery555-6542Rev. Timothy Lovejoy555 8904Ned Flanders636-555-3226Simpson,Homer5553642Dr. Julius Hibbert ”

試してみてください

import re
string = '1. A small sentence - 2.Anthoer tiny sentence. '
print('re.findall:',re.findall('sentence',string))
print('re.search:',re.search('sentence',string))
print('re.match:',re.match('sentence',string))
print('re.match:',re.match('1. A small sentence',string))
print('re.sub:',re.sub('small','large',string)) 
print('re.sub:',re.sub('small','',string)) 

出力:
re.findall: ['sentence', 'sentence']
re.search: <re.Match object; span=(11, 19), match='sentence'>
re.match: None
re.match: <re.Match object; span=(0, 19), match='1. A small sentence'>
re.sub: 1. A large sentence - 2.Anthoer tiny sentence.
re.sub: 1. A sentence - 2.Anthoer tiny sentence.

一般的な広義化記号
1、英語のピリオド “.”:改行文字 “\n” を除く任意の 1 文字を表すことができます;

string = '1. A small sentence - 2.Anthoer tiny sentence. '
re.findall('A.',string)

出力:['A ', 'An']

2、文字クラス “[]”:中括弧内に含まれる任意の文字がマッチします;

string = 'small smell smll smsmll sm3ll sm.ll sm?ll sm\nll sm\tll'
print('re.findall:',re.findall('sm.ll',string))
print('re.findall:',re.findall('sm[asdfg]ll',string))
print('re.findall:',re.findall('sm[a-zA-Z0-9]ll',string))
print('re.findall:',re.findall('sm\.ll',string))
print('re.findall:',re.findall('sm[.?]ll',string))

出力:

re.findall: ['small', 'smell', 'sm3ll', 'sm.ll', 'sm?ll', 'sm\tll']
re.findall: ['small']
re.findall: ['small', 'smell', 'sm3ll']
re.findall: ['sm.ll']
re.findall: ['sm.ll', 'sm?ll']

3. 量化記号 "{}": マッチする回数を指定できます。

print('re.findall:',re.findall('sm..ll',string))
print('re.findall:',re.findall('sm.{2}ll',string))
print('re.findall:',re.findall('sm.{1,2}ll',string))
print('re.findall:',re.findall('sm.{1,}ll',string))
print('re.findall:',re.findall('sm.?ll',string)) # {0,1}
print('re.findall:',re.findall('sm.+ll',string)) # {0,}
print('re.findall:',re.findall('sm.*ll',string)) # {1,}

出力:
re.findall: ['smsmll']
re.findall: ['smsmll']
re.findall: ['small', 'smell', 'smsmll', 'sm3ll', 'sm.ll', 'sm?ll', 'sm\tll']
re.findall: ['small smell smll smsmll sm3ll sm.ll sm?ll', 'sm\tll']
re.findall: ['small', 'smell', 'smll', 'smll', 'sm3ll', 'sm.ll', 'sm?ll', 'sm\tll']
re.findall: ['small smell smll smsmll sm3ll sm.ll sm?ll', 'sm\tll']
re.findall: ['small smell smll smsmll sm3ll sm.ll sm?ll', 'sm\tll']

ps:貪欲なルール、できるだけ多くをマッチさせる

完全なコード#

import pandas as pd
rawdata = '555-1239Moe Szyslak(636) 555-0113Burns, C.Montgomery555-6542Rev. Timothy Lovejoy555 8904Ned Flanders636-555-3226Simpson,Homer5553642Dr. Julius Hibbert'
names = re.findall('[A-Z][A-Za-z,. ]*',rawdata)
print(names)
number = re.findall('\(?[0-9]{0,3}\)?[ \-]?[0-9]{3}[ \-]?[0-9]{4}',rawdata)
print(number)
pd.DataFrame({'Name':names,'TelPhone':number})

出力:
ここに画像の説明を挿入

五、Xpath を使用してウェブページを解析#

XML パス言語(XML Path Language)は、XML に基づくツリー構造であり、データ構造ツリー内のノードを探し、XML 文書内の特定の部分の位置を特定するための言語です。Xpath を使用するには、lxml ライブラリから etree モジュールをインポートし、HTML クラスを使用してマッチさせる HTML オブジェクトを初期化する必要があります(XPath は文書の DOM 表現形式のみを処理できます)。HTML クラスの基本的な構文は以下の通りです。

1. 基本構文#

lxml.etree.HTML(text, parser=None, *, base_url=None)

パラメータ説明
textstr を受け取ります。HTML に変換する必要がある文字列を示します。デフォルト値はありません。
parserstr を受け取ります。選択した HTML パーサーを示します。デフォルト値はありません。
base_urlstr を受け取ります。文書の元の URL を設定し、外部エンティティの相対パスを検索するために使用されます。デフォルトは None です。
HTML 内のノードが閉じられていない場合、etree モジュールは自動補完機能も提供します。tostring メソッドを呼び出すことで修正された HTML コードを出力できますが、結果は bytes 型であり、decode メソッドを使用して str 型に変換する必要があります。

Xpath は、正規表現に似た式を使用して HTML ファイル内の内容をマッチさせます。一般的なマッチング式は以下の通りです。

説明
nodenamenodename ノードのすべての子ノードを選択します。
/現在のノードから直接の子ノードを選択します。
//現在のノードから子孫ノードを選択します。
.現在のノードを選択します。
..現在のノードの親ノードを選択します。
@属性を選択します。

2. 述語#

Xpath の述語は、特定のノードや特定の値を含むノードを検索するために使用され、述語はパスの後の角括弧内に埋め込まれます。以下の通りです。

説明
/html/body/div[1]body の子ノード下の最初の div ノードを選択します。
/html/body/div[last()]body の子ノード下の最後の div ノードを選択します。
/html/body/div[last()-1]body の子ノード下の倒数第二の div ノードを選択します。
/html/body/div[positon()<3]body の子ノード下の前の 2 つの div ノードを選択します。
/html/body/div[@id]body の子ノード下の id 属性を持つ div ノードを選択します。
/html/body/div[@id="content"]body の子ノード下の id 属性値が content の div ノードを選択します。
/html/body/div[xx>10.00]body の子ノード下の xx 要素値が 10 を超えるノードを選択します。

3. 機能関数#

Xpath には、あいまい検索を行うための機能関数も提供されています。オブジェクトの一部の特徴しか把握していない場合、あいまい検索を行うために機能関数を使用できます。具体的な関数は以下の通りです。

機能関数説明
starts-with//div[starts-with(@id,”co”)]id 値が co で始まる div ノードを選択します。
contains//div[contains(@id,”co”)]id 値に co を含む div ノードを選択します。
and//div[contains(@id,”co”)andcontains(@id,”en”)]id 値に co と en を含む div ノードを選択します。
text()//li[contains(text(),”first”)]ノードのテキストに first を含む div ノードを選択します。

4. Google 開発者ツールの使用#

Google 開発者ツールは、xpath パスを簡単にコピーする便利な方法を提供します。
ここに画像の説明を挿入
eg:Zhihu のホットリストをクローリングする完全なコード
Zhihu のホットリストをクローリングしてみましたが、ログインが必要なので、自分でログインしてから cookie を取得できます。

import requests
from lxml import etree
url = "https://www.zhihu.com/hot"
hd = { 'Cookie':'あなたのCookie', #'Host':'www.zhihu.com',
        'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'}

response = requests.get(url, headers=hd)
html_str = response.content.decode()
html = etree.HTML(html_str)
title = html.xpath("//section[@class='HotItem']/div[@class='HotItem-content']/a/@title")
href = html.xpath("//section[@class='HotItem']/div[@class='HotItem-content']/a/@href")
f = open("zhihu.txt",'r+')
for i in range(1,41):
    print(i,'.'+title[i])
    print('リンク:'+href[i])
    print('-'*50)
    f.write(str(i)+'.'+title[i]+'\n')
    f.write('リンク:'+href[i]+'\n')
    f.write('-'*50+'\n')
f.close()

クローリング結果
ここに画像の説明を挿入

六、データの保存#

1. json 形式で保存#

import requests
from lxml import etree
import json
#上記のコードは省略
with open('zhihu.json','w') as j:
    json.dump({'title':title,'hrefL':href},j,ensure_ascii=False)

保存結果(ps: ファイル形式処理後)
ここに画像の説明を挿入

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。