https://tt.mop.com/c44/0/1_{頁碼}.html(共 100 頁)。Control.Invoke 回主執行緒刷新 Label。public static class HttpRequestUtil
{
private static readonly string UserAgent =
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)";
public static string GetPageHtml(string url)
{
var req = (HttpWebRequest)WebRequest.Create(url);
req.UserAgent = UserAgent;
req.Proxy = null; // 避免公司代理干擾
using (var resp = (HttpWebResponse)req.GetResponse())
using (var stream = resp.GetResponseStream())
using (var reader = new StreamReader(stream, Encoding.UTF8))
return reader.ReadToEnd();
}
public static void HttpDownloadFile(string url)
{
int pos = url.LastIndexOf('/') + 1;
string fileName = url.Substring(pos);
string dir = Path.Combine(Application.StartupPath, "download");
Directory.CreateDirectory(dir);
string filePath = Path.Combine(dir, fileName);
if (File.Exists(filePath)) return;
var req = (HttpWebRequest)WebRequest.Create(url);
req.UserAgent = UserAgent;
req.Proxy = null;
using (var resp = (HttpWebResponse)req.GetResponse())
using (var respStream = resp.GetResponseStream())
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
byte[] buffer = new byte[1024];
int read;
while ((read = respStream.Read(buffer, 0, buffer.Length)) > 0)
fileStream.Write(buffer, 0, read);
}
}
}
public partial class Form1 : Form
{
private readonly List _threads = new List();
private DateTime _startTime;
private int _completedPages, _totalPersons, _totalImages;
private void btnStart_Click(object sender, EventArgs e)
{
_startTime = DateTime.Now;
_completedPages = _totalPersons = _totalImages = 0;
UpdateUI();
btnStart.Enabled = false;
btnPause.Enabled = btnStop.Enabled = true;
for (int i = 1; i <= 10; i++)
{
int threadIndex = i;
var t = new Thread(() => Worker(threadIndex));
t.Start();
_threads.Add(t);
}
}
private void Worker(int threadIndex)
{
var regUserLink = new Regex(@"]*?\s+href=""([^""]*?)""");
var regImg = new Regex(@"\s*
{ MessageBox.Show("完成!"); ResetUI(); }));
}
}
// --- UI 更新委派 ---
private void UpdatePageLabel() =>
Invoke(new Action(() => lblPage.Text = $"已完成頁數:{_completedPages}"));
private void UpdatePersonLabel() =>
Invoke(new Action(() => lblPerson.Text = $"已完成條數:{_totalPersons}"));
private void UpdateSpeedLabel()
{
double secs = (DateTime.Now - _startTime).TotalSeconds;
double speed = secs > 0 ? _totalImages / secs : 0;
Invoke(new Action(() => lblSpeed.Text = $"速度:{speed:F1} 張/秒"));
}
private void ResetUI()
{
btnStart.Enabled = true;
btnPause.Enabled = btnStop.Enabled = false;
}
// --- 暫停/繼續/停止 ---
private void btnPause_Click(object sender, EventArgs e)
{
foreach (var t in _threads)
if (t.ThreadState == ThreadState.Running) t.Suspend(); // 注意:Suspend 已過時
btnPause.Enabled = false; btnResume.Enabled = true;
}
private void btnResume_Click(object sender, EventArgs e)
{
foreach (var t in _threads)
if (t.ThreadState == ThreadState.Suspended) t.Resume();
btnPause.Enabled = true; btnResume.Enabled = false;
}
private void btnStop_Click(object sender, EventArgs e)
{
foreach (var t in _threads) t.Abort(); // 同樣過時,教學用
ResetUI();
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
foreach (var t in _threads) t.Abort();
base.OnFormClosing(e);
}
}
| 技術點 | 說明 |
|---|---|
| Regex 解析 HTML | 簡單頁面可行,結構變動極易失效。生產環境**強烈建議**用 HtmlAgilityPack 或 AngleSharp 進行 DOM 解析。 |
| Thread.Suspend/Resume/Abort | **已標記過時且不安全**(可能導致鎖死、資源洩漏)。現代寫法應用 CancellationToken + Task/Parallel.ForEach 協作式取消。 |
| Interlocked.Increment | 無鎖原子計數,效能優於 lock,適合高頻計數器。 |
| Control.Invoke | WinForms 要求跨執行緒操作 UI 必須回主執行緒,否則拋出異常。 |
| User-Agent 偽裝 | 模擬 IE 8 繞過簡單反爬;實際需視目標站策略輪換 UA、Cookie、IP。 |
| 禮貌延遲 | Thread.Sleep(1) 降低目標伺服器壓力,減少被封風險。 |
// 使用 HttpClient + HtmlAgilityPack + Task + CancellationToken
var client = new HttpClient { DefaultRequestHeaders = { UserAgent = "..." } };
var cts = new CancellationTokenSource();
var tasks = Enumerable.Range(1, 100)
.Select(page => DownloadPageAsync(page, client, cts.Token))
.ToList();
await Task.WhenAll(tasks); // 或用 Parallel.ForEachAsync 限制併發度
async Task DownloadPageAsync(int page, HttpClient http, CancellationToken ct)
{
string html = await http.GetStringAsync($"https://tt.mop.com/c44/0/1_{page}.html", ct);
var doc = new HtmlDocument(); doc.LoadHtml(html);
var userLinks = doc.DocumentNode.SelectNodes("//a[@class='J-userPic']/@href");
// ... 後續同理,全部 async/await,零阻塞執行緒
}
本範例展示了 **HttpWebRequest + Regex + 手動 Thread 管理** 的傳統爬蟲寫法,適合理解底層運作原理。但實際專案請務必遷移至 **HttpClient + HtmlAgilityPack/AngleSharp + Task/Parallel + CancellationToken** 技術棧,並加入重試、限流、代理池、分散式佇列等機制,構建穩健、可維護、合規的爬蟲系統。