思路
爬虫难点一是登录问题,二是请求频繁会弹出验证确定是人在操作。登录的可以将手机登录二维码发送到手机,通过手机确认。弹出的验证用人工智能应该是可以解决,不知道现在gpt支持图文,能不能破解这些验证。这两个问题复杂的问题,我们就用人工去操作。我们可以自己创建一个浏览器,自己手动登录,手动认证,爬取数据交给程序,页面上的操作交和查询结果交给js脚本。
操作
我是在window下操作,创建一个浏览器那不是用c#比较简单,在Visual Studio 创建一个窗体程序,托一个浏览器控件,这不有了。浏览器控件使用的是WebView2,通过NuGet安装,不知道为什么要重新打开项目才在工具显示控件。将WebView2托到窗口中间,添加后退和刷新控件。
在Form1_Load中初始化WebView2,设置浏览器初始加载淘宝地址
//初始化
webView21.EnsureCoreWebView2Async(null);
// 设置初始 URL
webView21.Source = new Uri("https://www.taobao.com/");
在后退按钮和刷新按钮,添加事件
//刷新
webView21.Refresh();
//后退
webView21.GoBack();
添加一个画板有商品名称输入框,开始抓取按钮,进读条,查看结果按钮
主要按钮是开始抓取,他的大概流程是按钮点击时,会将商品名称的输入框输入到淘宝页面的搜索框,然后点击淘宝页面上的查询,如果淘宝页面没有登录的话,会弹出个登录界面,停止了抓取。登录了的话,就会获取当前查询的结果,拿到结果就会判断有没有下一页,有下一页继续点击下页,继续获取当前查询的结果,直到没有下页停止获取。
这些操作都是在淘宝页面上操作,类似人在操作了,爬虫为了不被发现,不就是伪装人那样。下面就是开始抓取事件代码:
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);
}
}