C#简单爬取淘宝商品信息

363 阅读4分钟

思路

爬虫难点一是登录问题,二是请求频繁会弹出验证确定是人在操作。登录的可以将手机登录二维码发送到手机,通过手机确认。弹出的验证用人工智能应该是可以解决,不知道现在gpt支持图文,能不能破解这些验证。这两个问题复杂的问题,我们就用人工去操作。我们可以自己创建一个浏览器,自己手动登录,手动认证,爬取数据交给程序,页面上的操作交和查询结果交给js脚本。

操作

我是在window下操作,创建一个浏览器那不是用c#比较简单,在Visual Studio 创建一个窗体程序,托一个浏览器控件,这不有了。浏览器控件使用的是WebView2,通过NuGet安装,不知道为什么要重新打开项目才在工具显示控件。将WebView2托到窗口中间,添加后退和刷新控件。

QQ截图20240223103126.png

Form1_Load中初始化WebView2,设置浏览器初始加载淘宝地址

//初始化
webView21.EnsureCoreWebView2Async(null);
// 设置初始 URL
webView21.Source = new Uri("https://www.taobao.com/");

在后退按钮和刷新按钮,添加事件

//刷新
webView21.Refresh();
//后退
webView21.GoBack();

添加一个画板有商品名称输入框,开始抓取按钮,进读条,查看结果按钮

QQ截图20240223104624.png

主要按钮是开始抓取,他的大概流程是按钮点击时,会将商品名称的输入框输入到淘宝页面的搜索框,然后点击淘宝页面上的查询,如果淘宝页面没有登录的话,会弹出个登录界面,停止了抓取。登录了的话,就会获取当前查询的结果,拿到结果就会判断有没有下一页,有下一页继续点击下页,继续获取当前查询的结果,直到没有下页停止获取。 这些操作都是在淘宝页面上操作,类似人在操作了,爬虫为了不被发现,不就是伪装人那样。下面就是开始抓取事件代码:

private async void button3_Click(object sender, EventArgs e)
{
    if (runStatus == 0)
    {
        string sreachText = textBox1.Text;

        if (sreachText.Length == 0) 
        {

            MessageBox.Show("请输入商品名称。");
            return;
        }

        runStatus = 1;
        button3.Text = "停止爬取";

        data = new JArray();

        //先点击搜索
        sreach(sreachText);

        while (runStatus == 1) {
            //有可能dom没渲染完,接收响应要时间,所以循环休眠获取结果
            while (true)
            {
                await Task.Delay(500);
                var array = await queryResult();
                if (array != null)
                {
                    //添加到data集合中
                    data.Merge(array);
                    break;
                }

                //拿不到结果,可能是没有登录
                bool status = await isLogin();
                if (status == false)
                {
                    runStatus = 0;
                    button3.Text = "开始爬取";

                    MessageBox.Show("请先登录");
                    return;

                }

            }

            //更新进度
            int n = await queryBar();
            progressBar1.Value = n;
            label3.Text = $"{n}%";


            //查看是否有下一页
            bool t = await checkNextPage();
            if (t == false)
            {
                runStatus = 0;
                button3.Text = "开始爬取";

                //爬完了
                break;
            }
        }

    }
    else {
        runStatus = 0;
        button3.Text = "开始爬取";
    }
}

那些点击搜索查询搜索结果,判断是否登录,查看是否有下一页实际上是都是把js脚本,扔到淘宝页面上执行,代码如下:

//搜索
private async void sreach(string text) 
{
    string search = $@"
        document.getElementById('q').value = '{text}';
        document.querySelector('.btn-search').click()
        ";
    await webView21.CoreWebView2.ExecuteScriptAsync(search);

}

//查询搜索结果
private async Task<JArray> queryResult() 
{
    JArray array = null;
    //查询搜索结果
    string script = @"
    var array = null;

    if(!document.querySelector('.UnusualStatus--wrap--kbi8ePx')){

        if(document.querySelector('.Title--title--jCOPvpf span')){
            array =[];
            var elements = document.querySelectorAll('.Content--contentInner--QVTcU0M > div');

            for (const element of elements) {
                if(!element.querySelector('.Title--title--jCOPvpf span')){
                    break;
                }
                var name = element.querySelector('.Title--title--jCOPvpf span').innerText;
                var price = element.querySelector('.Price--priceInt--ZlsSi_M').innerText + element.querySelector('.Price--priceFloat--h2RR0RK').innerText;
                var area = element.querySelector('.Price--procity--_7Vt3mX').innerText;
                var storeName = element.querySelector('.ShopInfo--TextAndPic--yH0AZfx a').innerText;
                var payNum = element.querySelector('.Price--realSales--FhTZc7U').innerText;

                var p = {name:name,price:price,area:area,storeName:storeName,payNum:payNum}
                array.push(p)
            }
        }



    }else{
        array =[];
    }
    array
    ";
    string json = await webView21.CoreWebView2.ExecuteScriptAsync(script);
    if (!json.Equals("null"))
    {
        array = JArray.Parse(json);
    }
    return array;
}

//判断是否登录
private async Task<bool> isLogin()
{
    string isLoginScript = @"
    var isLogin = true;
    var iframes = document.querySelectorAll('iframe');
    for(var iframe of iframes) {
        var src = iframe.getAttribute('src')
        var d = window.getComputedStyle(iframe).display
        if( d === 'block' && src.indexOf('login.jhtml')!=-1){
            isLogin = false;
            break;
        }
    }
    if(isLogin){
        var divElement = document.querySelector('.login-box-warp');
        if(divElement){
            var computedStyle = window.getComputedStyle(divElement)
            if(computedStyle.display == 'block'){
	            isLogin = false;
            }
        }
    }
    isLogin
    ";

    string loginStatus = await webView21.CoreWebView2.ExecuteScriptAsync(isLoginScript);
    return loginStatus.Equals("true");

}

//查看完成进度
private async Task<int> queryBar() 
{
    string barScript = @"
    var n = document.querySelector('.next-pagination-list .next-current span').innerText;
    var l = document.querySelector('.next-pagination-list :last-child span').innerText;
    var r = Math.floor(n/l*100);
    r
    ";
    string bar = await webView21.CoreWebView2.ExecuteScriptAsync(barScript);
    if (bar.Equals("null")) 
    {
        bar = "100";
    }

   return int.Parse(bar);
}

//查看是否有下一页,有就翻页
private async Task<bool> checkNextPage()
{
    string nextPageScript = @"
                var nextPage = false;
                var nextDoc = document.querySelector('.next-pagination-list .next-current').nextSibling;
                if(nextDoc){
                    nextDoc.click();
                    nextPage = true;
                }
                nextPage;
            ";
    string nextStatus = await webView21.CoreWebView2.ExecuteScriptAsync(nextPageScript);
    return nextStatus.Equals("true");
}

查看结果按钮,就是把JArray集合在另一个窗口显示,代码如下:

public Content(JArray data)
{
    this.data = data;
    InitializeComponent();

    // 设置 ListView 的视图模式为详细信息模式
    listView1.View = View.Details;
    // 添加列头
    listView1.Columns.Add("序号", 60);
    listView1.Columns.Add("名称", 240);
    listView1.Columns.Add("价格", 80);
    listView1.Columns.Add("地区", 80);
    listView1.Columns.Add("店名", 120);
    listView1.Columns.Add("付款人数", 120);
}

private void Content_Load(object sender, EventArgs e)
{
    for (int i = 0; i < data.Count; i++)
    {
        // 获取JSON对象的属性
        string name = (string)data[i]["name"];
        string price = (string)data[i]["price"];
        string area = (string)data[i]["area"];
        string storeName = (string)data[i]["storeName"];
        string payNum = (string)data[i]["payNum"];

        ListViewItem item = new ListViewItem(i + "");
        item.SubItems.Add(name);
        item.SubItems.Add(price);
        item.SubItems.Add(area);
        item.SubItems.Add(storeName);
        item.SubItems.Add(payNum);
        listView1.Items.Add(item);
    }
}

最后效果

QQ截图20240223120221.png

完整代码

供个人学习 断续/CrawlerTBDemo - 码云 - 开源中国 (gitee.com)