ウェブスクレイピングを一定期間行っていると、ジオフェンスやIP禁止によってブロックされたウェブサイトに遭遇したことがあるかもしれません。プロキシサーバーは、あなたの正体を隠し、禁止されたリソースへのアクセスを許可することで、こうした状況に対処するのに役立ちます。
Rustプロキシサーバーは以下を容易に実現します:
- IP禁止回避:新しいプロキシIPで禁止措置を迂回し、スクレイピングを再開。
- ジオブロックの回避:他国のコンテンツに興味がある場合、ローカルプロキシが一時的なオンライン市民権を付与し、制限されたコンテンツにアクセス可能にします。
- 匿名性の確保:プロキシサーバーが実際のIPアドレスを隠蔽し、プライバシーを監視の目に晒されずに保護します。
これは氷山の一角に過ぎません!Rustの強力なライブラリと堅牢な構文により、プロキシの設定と管理は驚くほど簡単です。本記事では、プロキシサーバーの全容と、Rustでウェブスクレイピングにプロキシサーバーを活用する方法を解説します。
Rustでのプロキシサーバーの使用
Rustでプロキシサーバーを使用するには、まず設定が必要です。このチュートリアルでは、ローカルマシン上のNginxサーバーにプロキシを設定し、Rustバイナリからスクレイピングサンドボックス(例:https://toscrape.com/)へスクレイピングリクエストを送信する方法を解説します。
まず、ローカルシステムにNginx をインストールします。Linux の場合、Homebrew を使用して次のコマンドでインストールできます:
sudo apt install nginx
次に、以下のコマンドでサーバーを起動します:
nginx
次に、特定の場所に対するプロキシとして動作するようサーバーを設定します。例えば、/ に対するプロキシとして機能させ、処理する各リクエストにヘッダー(例: X-Proxy-Server)を追加するように設定できます。そのためには、nginx.conf ファイルを編集する必要があります。
ファイルの場所はホストOSによって異なります。詳細はNginxのドキュメントを参照してください。Linuxの場合、nginx.conf は/etc/nginx/nginx.confにあります。このファイルを開き、http.serverオブジェクトに以下のコードブロックを追加します:
http {
server {
# 以下のブロックを追加
location / {
resolver 8.8.8.8;
proxy_pass http://$http_host$request_uri;
proxy_set_header 'X-Proxy-Server' 'Nginx';
}
}
}
これにより、プロキシはすべての着信リクエストを元のURLに転送すると同時に、リクエストにヘッダーを追加するよう設定されます。ターゲットサーバーのログにアクセスできる場合、このヘッダーを確認することで、リクエストがプロキシ経由かクライアント直接かを検証できます。
次に、以下のコマンドを実行して Nginx サーバーを再起動します:
nginx -s reload
これで、スクレイピング用のプロキシとして使用できる状態になりました。
Rustでのウェブスクレイピングプロジェクトの作成
新しいスクレイピングプロジェクトを設定するには、以下のコマンドを実行してCargoで新しいRustバイナリを作成します:
cargo new rust-スクレイパー
プロジェクト作成後、3つのcrateを追加します。まずreqwestと スクレイパーを追加します。reqwestはターゲットリソースへのリクエスト送信に、スクレイパーは reqwestが受信したHTMLから必要なデータを抽出するために使用します。次に3つ目のcrateであるtokioを追加し、reqwestを介した非同期ネットワーク呼び出しを処理します。
これらをインストールするには、プロジェクトディレクトリ内で次のコマンドを実行します:
cargo add スクレイパー reqwest tokio --features "reqwest/blocking tokio/full"
次に、src/main.rsファイルを開き、以下のコードを追加します:
use reqwest;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>>{
let url = "http://books.toscrape.com/";
let client = reqwest::Client::new();
let response = client
.get(url)
.send()
.await?;
let html_content = response.text().await?;
extract_products(&html_content);
Ok(())
}
fn extract_products(html_content: &str) {
let document = scraper::Html::parse_document(&html_content);
let html_product_selector = scraper::Selector::parse("article.product_pod").unwrap();
let html_products = document.select(&html_product_selector);
let mut products: Vec<PRODUCT> = Vec::new();
for html_product in html_products {
let url = html_product
.select(&scraper::Selector::parse("a").unwrap())
.next()
.and_then(|a| a.value().attr("href"))
.map(str::to_owned);
let image = html_product
.select(&scraper::Selector::parse("img").unwrap())
.next()
.and_then(|img| img.value().attr("src"))
.map(str::to_owned);
let name = html_product
.select(&scraper::Selector::parse("h3").unwrap())
.next()
.map(|title| title.text().collect::<STRING>());
let price = html_product
.select(&scraper::Selector::parse(".price_color").unwrap())
.next()
.map(|price| price.text().collect::<STRING>());
let product = Product {
url,
image,
name,
price,
};
products.push(product);
}
println!("{:?}", products);
}
#[derive(Debug)]
struct Product {
url: Option<String>,
image: Option<String>,
name: Option<String>,
price: Option<String>,
}
このコードはreqwest クレートを使用してクライアントを作成し、URLhttps://books.toscrape.com のウェブページを取得します。その後、extract_products という関数内でページの HTML を処理し、ページから製品リストを抽出します。抽出ロジックはスクレイパー クレートを使用して実装されており、プロキシの使用の有無にかかわらず変更されません。
それでは、このバイナリを実行して製品リストが正しく抽出されるか試してみましょう。以下のコマンドを実行してください:
cargo run
ターミナルには以下のような出力が表示されるはずです:
Finished dev [unoptimized + debuginfo] target(s) in 0.80s
Running `target/debug/rust_scraper`
[Product { url: Some("catalogue/a-light-in-the-attic_1000/index.html"), image: Some("media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg"), name: Some("A Light in the ..."), price: Some("£51.77") }, Product { url: Some("catalogue/tipping-the-velvet_999/index.html"), image: Some("media/cache/26/0c/260c6ae16bce31c8f8c95daddd9f4a1c.jpg"), name: Some("ティッピング・ザ・ベルベット"), price: Some("£53.74") }, Product { url: Some("catalogue/soumission_998/index.html"), image: Some("media/cache/3e/ef/3eef99c9d9adef34639f510662022830.jpg"), name: Some("Soumission"), price: Some("£50.10") }, Product { url: Some("catalogue/sharp-objects_997/index.html"), image: Some("media/cache/32/51/3251cf3a3412f53f339e42cac2134093.jpg"), name: Some("シャープ・オブジェクツ"), price: Some("£47.82") }, Product { url: Some("catalogue/sapiens-a-brief-history-of-humankind_996/index.html"), image: Some("media/cache/be/a5/bea5697f2534a2f86a3ef27b5a8c12a6.jpg"), name: Some("Sapiens: A Brief History ..."), price: Some("£54.23") }, Product { url: Some("catalogue/the-requiem-red_995/index.html"), image: Some("media/cache/68/33/68339b4c9bc034267e1da611ab3b34f8.jpg"), name: Some("レクイエム・レッド"), price: Some("£22.65") }, Product { url: Some("catalogue/the-dirty-little-secrets-of-getting-your-dream-job_994/index.html"), image: Some("media/cache/92/27/92274a95b7c251fea59a2b8a78275ab4.jpg"), name: Some("The Dirty Little Secrets ..."), price: Some("£33.34") }, Product { url: Some("catalogue/the-coming-woman-a-novel-based-on-the-life-of-the-infamous-feminist-victoria-woodhull_993/index.html"), image: Some("media/cache/3d/54/3d54940e57e662c4dd1f3ff00c78cc64.jpg"), name: Some("来るべき女性: ヴィクトリア・ウッドハル伝記小説 ..."), price: Some("£17.93") }, Product { url: Some("catalogue/the-boys-in-the-boat-nine-americans-and-their-epic-quest-for-gold-at-the-1936-berlin-olympics_992/index.html"), image: Some("media/cache/66/88/66883b91f6804b2323c8369331cb7dd1.jpg"), name: Some("The Boys in the ..."), price: Some("£22.60") }, Product { url: Some("catalogue/the-black-maria_991/index.html"), image: Some("media/cache/58/46/5846057e28022268153beff6d352b06c.jpg"), name: Some("The Black Maria"), price: Some("£52.15") }, Product { url: Some("catalogue/starving-hearts-triangular-trade-trilogy-1_990/index.html"), image: Some("media/cache/be/f4/bef44da28c98f905a3ebec0b87be8530.jpg"), name: Some("Starving Hearts (Triangular Trade ..."), price: Some("£13.99") }, Product { url: Some("catalogue/shakespeares-sonnets_989/index.html"), image: Some("media/cache/10/48/1048f63d3b5061cd2f424d20b3f9b666.jpg"), name: Some("Shakespeare's Sonnets"), price: Some("£20.66") }, Product { url: Some("catalogue/set-me-free_988/index.html"), image: Some("media/cache/5b/88/5b88c52633f53cacf162c15f4f823153.jpg"), name: Some("Set Me Free"), price: Some("£17.46") }, Product { url: Some("catalogue/scott-pilgrims-precious-little-life-scott-pilgrim-1_987/index.html"), image: Some("media/cache/94/b1/94b1b8b244bce9677c2f29ccc890d4d2.jpg"), name: Some("スコット・ピルグリムの尊い小さな人生..."), price: Some("£52.29") }, Product { url: Some("カタログ/リプ・イット・アップ・アンド・スタート・アゲイン_986/index.html"), image: Some("media/cache/81/c4/81c4a973364e17d01f217e1188253d5e.jpg"), name: Some("リプ・イット・アップ・アンド・スタート・アゲイン"), price: Some("£35.02") }, Product { url: Some("catalogue/our-band-could-be-your-life-scenes-from-the-american-indie-underground-1981-1991_985/index.html"), image: Some("media/cache/54/60/54607fe8945897cdcced0044103b10b6.jpg"), name: Some("Our Band Could Be ..."), price: Some("£57.25") }, Product { url: Some("catalogue/olio_984/index.html"), image: Some("media/cache/55/33/553310a7162dfbc2c6d19a84da0df9e1.jpg"), name: Some("Olio"), price: Some("£23.88") }, Product { url: Some("catalogue/mesaerion-the-best-science-fiction-stories-1800-1849_983/index.html"), image: Some("media/cache/09/a3/09a3aef48557576e1a85ba7efea8ecb7.jpg"), name: Some("Mesaerion: The Best Science ..."), price: Some("£37.59") }, Product { url: Some("catalogue/libertarianism-for-beginners_982/index.html"), image: Some("media/cache/0b/bc/0bbcd0a6f4bcd81ccb1049a52736406e.jpg"), name: Some("Libertarianism for Beginners"), price: Some("£51.33") }, Product { url: Some("catalogue/its-only-the-himalayas_981/index.html"), image: Some("media/cache/27/a5/27a53d0bb95bdd88288eaf66c9230d7e.jpg"), name: Some("It's Only the Himalayas"), price: Some("£45.17") }]
これはスクレイピングロジックが正しく動作していることを意味します。これで、このスクレイパーに Nginx プロキシを追加する準備が整いました。
プロキシの活用
main()関数内でスクレイピングリクエストが単発のget呼び出しではなく、本格的なreqwestクライアント経由で送信されていることに気づくでしょう。これはクライアント作成時にプロキシを容易に設定できることを意味します。
クライアントを設定するには、以下のコード行を更新します:
async fn main() -> Result<(), Box<dyn Error>>{
let url = "https://books.toscrape.com/";
# この行を置き換える
let client = reqwest::Client::new();
# この行に置き換える
let client = reqwest::Client::builder()
.proxy(reqwest::Proxy::https("http://localhost:8080")?)
.build()?;
//...
Ok(())
}
reqwestを使用してプロキシを設定する際、一部のプロキシプロバイダー(Bright Dataを含む)はhttpとhttpsの両方の設定で動作しますが、追加の設定が必要な場合があることを理解することが重要です。https使用時に問題が発生した場合は、httpに切り替えてアプリを実行してみてください。
次に、cargo run コマンドを使用してバイナリを再度実行してみてください。以前と同様の応答が得られるはずです。ただし、Nginx サーバーのログを確認し、リクエストがプロキシ経由で送信されたかどうかを確認してください。
お使いのオペレーティングシステムに応じた手順でNginxサーバーのログを特定してください。MacのHomebrewベースのインストール環境では、アクセスログとエラーログは/opt/homebrew/var/log/nginxフォルダにあります。access.logファイルを開くと、ファイル末尾に以下のような行が表示されるはずです:
127.0.0.1 - - [07/Jan/2024:05:19:54 +0530] "GET https://books.toscrape.com/ HTTP/1.1" 200 18 "-" "-"
これはリクエストが Nginx サーバーを経由してプロキシされたことを示しています。これでリモートホストにサーバーを設定し、地理的制限や IP ブロックを回避するために利用できるようになります。
ローテーションプロキシ
ウェブスクレイピングプロジェクトでは、複数のプロキシをローテーションさせる必要がある場合があります。これにより、スクレイピングの負荷を複数のIPに分散でき、単一ソースや場所からの高トラフィックによる検知を回避できます。
ローテーションプロキシを実装するには、main.rsファイルに以下の関数を追加します:
#[derive(Debug)]
struct Proxy {
ip: String,
port: String,
}
fn get_proxies() -> Vec<PROXY> {
let mut proxies = Vec::new();
proxies.push(Proxy {
ip: "http://localhost".to_string(),
port: "8082".to_string(),
});
// プロキシセットを拡大するには、ここにさらにproxies.push文を追加
proxies
}
これによりプロキシのセットを簡単に定義できます。次に、ランダム化されたプロキシを利用するためにmain関数を次のように更新します:
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let url = "https://books.toscrape.com/";
// 以下の2行を追加
let proxies = get_proxies();
let random_proxy = proxies.choose(&mut rand::thread_rng()).unwrap();
let client = reqwest::Client::builder()
// 以下の行を以下のように更新
.proxy(reqwest::Proxy::http(format!("{0}:{1}", random_proxy.ip, random_proxy.port))?)
.build()?;
// 残りは変更なし
let response = client
.get(url)
.send()
.await?;
let html_content = response.text().await?;
extract_products(&html_content);
Ok(())
}
次に、プロキシ配列からランダムにプロキシを選択できるようにするには、randクレートをインストールする必要があります。以下のコマンドを実行してインストールできます:
cargo add rand
次に、main.rsファイルの先頭に次の行を追加してrandクレートをインポートします:
use rand::seq::SliceRandom;
次に、cargo run コマンドでバイナリを再度実行し、動作を確認してください。以前と同じ出力が表示されれば、ランダム化されたプロキシリストが正しく設定されていることを示します。
Bright Data プロキシサーバー
ご覧の通り、プロキシを手動で設定するのは非常に手間がかかります。さらに、新しいIPアドレスとロケーションの利点をすべて活用するには、プロキシサーバーをリモートサーバーでホストする必要があります。こうした手間を避けたい場合は、Bright Dataのプロキシサーバーの利用をご検討ください。
無数のプロキシプロバイダーが存在する中、Bright Dataはその圧倒的な規模と柔軟性で知られています。Bright Dataでは、195カ国に分散した150 million+のレジデンシャルプロキシ、モバイルプロキシ、データセンタープロキシ、ISPプロキシからなる広大なネットワークを利用できます。膨大な数のレジデンシャルプロキシにより、特定の国、都市、さらにはモバイルキャリアをターゲットにした精密なスクレイピングが可能です。
さらに、Bright Dataのレジデンシャルプロキシは実際のユーザートラフィックと完全に同化します。一方、データセンターとモバイルオプションは驚異的な速度と安定した接続を提供します。Bright Dataの自動ローテーション機能はスクレイピングを機敏に保ち、検知や禁止措置のリスクを最小限に抑えます。
ご自身でお試しいただくには、https://brightdata.com/ にアクセスし、右上の「無料トライアル」をクリックしてください。サインアップ後、コントロールパネルページに移動します:

このページで「プロキシ製品を表示」をクリックすると、「プロキシ&スクレイピングインフラ」ページに移動します:

このページには、これまでにプロビジョニングしたすべてのプロキシが表示されます。プロキシを追加するには、右上の青い「追加」ボタンをクリックし、「レジデンシャルプロキシ」を選択します:

新しいレジデンシャルプロキシを設定できるフォームが表示されます。デフォルト設定のまま、ページ下部までスクロールし「追加」をクリックしてください。
レジデンシャルプロキシが作成されると、新規プロキシの詳細が表示されるページに移動します。「アクセスパラメータ」タブをクリックし、プロキシのホスト、ユーザー名、パスワードを確認します:

これらのパラメータを使用して、Rustバイナリにプロキシを統合できます。そのためには、src/main.rsファイル内のmain ()関数を次のように更新します:
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let url = "https://books.toscrape.com/";
// Bright Dataプロキシ詳細ページの情報で以下のブロックを更新
let client = reqwest::Client::builder()
.proxy(reqwest::Proxy::http("<BDプロキシホスト名 & ポート>")?
.basic_auth("<BDユーザー名>", "<BDパスワード>"))
.build()?;
// 残りの部分は変更なし
let response = client
.get(url)
.send()
.await?;
let html_content = response.text().await?;
extract_products(&html_content);
Ok(())
}
次にバイナリを再度実行してみてください。以前と同様に正しくレスポンスが返されるはずです。ここでの主な違いは、リクエストがBright Data経由でプロキシされ、あなたの身元と実際の位置情報が隠蔽される点です。
以下のコードスニペットでクライアントのIPアドレスを表示するAPIにリクエストを送信し、これを確認できます:
use reqwest;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let url = "http://lumtest.com/myip.json";
// Bright Dataプロキシ詳細ページの情報で以下のブロックを更新
let client = reqwest::Client::builder()
.proxy(reqwest::Proxy::http("<BDプロキシホスト名 & ポート>")?
.basic_auth("<BDユーザー名>", "<BDパスワード>"))
.build()?;
// 残りの部分は変更なし
let response = client
.get(url)
.send()
.await?;
let html_content = response.text().await?;
println!("{:?}", html_content);
Ok(())
}
cargo run コマンドでコードを実行すると、以下のような出力が表示されるはずです:
"{"ip":"209.169.64.172","country":"US","asn":{"asnum":6300,"org_name":"CCI-TEXAS"},"geo":{"city":"Conroe","region":"TX","region_name":"Texas","postal_code":"77304","latitude":30.3228,"longitude":-95.5298,"tz":"America/Chicago","lum_city":"conroe","lum_region":"tx"}}"
これは、ページをクエリするために使用しているプロキシサーバーのIPと位置情報を反映します。
まとめ
この記事では、Rustでプロキシを使用する方法について学びました。プロキシはデジタルマスクのようなもので、オンライン上の制限を回避し、ウェブサイトの制限の背後を覗くことを可能にします。また、ウェブを閲覧する際の匿名性を維持することもできます。
ただし、プロキシを独自に設定するのは複雑なプロセスです。通常は、Bright Dataのような確立されたプロキシプロバイダーを利用することが推奨されます。同社は150 million+の使いやすいプロキシプールを提供しています。