上周三下午三点多我正摸鱼刷抖音看小姐姐跳舞呢突然微信弹出来个视频通话震得我鼠标都掉了。是我大学同学老王去年辞职开了个淘宝店卖那种9.9包邮的家居小玩意最近生意不太好想爬一下同类目top100的商品数据看看人家的定价、销量、标题都是怎么弄的。他说“威哥这对你来说不是小菜一碟吗半小时搞定吧晚上我请你吃烧烤烤腰子管够”我一听有烤腰子立马就精神了拍着胸脯说“没问题你等着半小时后发你Excel。”结果呢我从下午三点半搞到凌晨一点半烧烤也没吃上还差点把自己搞进去。中间踩的坑能绕我家小区三圈今天必须写下来给你们这些天天问我“为什么我爬的页面没有数据”的新手好好上一课。别再傻了吧唧爬HTML了动态页面根本没数据我当时打开淘宝搜了个“厨房置物架”页面加载出来之后满屏都是商品看着挺正常的。然后我熟练地打开PyCharm新建了个文件噼里啪啦写了几行代码importrequests urlhttps://s.taobao.com/search?q厨房置物架headers{User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36}responserequests.get(url,headersheaders)print(response.text)运行之后我傻眼了。返回的HTML里除了一堆script标签和css样式连个商品标题的影子都没有。我第一反应是反爬肯定是headers不够全。然后我把浏览器里的所有headers都复制过来了包括cookie、referer、accept-language之类的密密麻麻写了十几行比我脸都长。再运行还是一样的结果。我又试了加代理IP换了三个不同地区的代理还是不行。我甚至把requests换成了urllib结果还是一样的。折腾了一个多小时我都快怀疑人生了难道淘宝的反爬已经这么牛逼了吗连个搜索页都爬不了就在我准备放弃跟老王说我搞不定的时候我突然一拍大腿我靠我是不是傻了这他妈是动态页面啊现在哪个电商网站还用服务端渲染啊全都是Ajax异步加载数据然后前端渲染到页面上的。你直接爬HTML能拿到数据才怪了我当时真想给自己一巴掌干了十年爬虫居然犯这种低级错误说出去都丢人。然后我打开F12开发者工具切到Network标签把过滤器改成XHR然后刷新页面。果然一堆请求刷刷地出来了跟跑马灯似的。我一个个点进去看响应终于找到了那个返回商品数据的请求https://s.taobao.com/api?_ksTS1716200000000_123callbackjsonp124apimtop.relationrecommend.WirelessRecommendappkey12574478data%7B%22q%22%3A%22%E5%8E%A8%E6%88%BF%E7%BD%AE%E7%89%A9%E6%9E%B6%22%7D响应是一个JSONP格式的数据去掉前面的jsonp124(和后面的)就是标准的JSON里面的resultList数组就是所有的商品信息标题、价格、销量、图片、店铺名应有尽有。我当时差点哭出来终于找到你了然后我把这个请求的URL和参数都复制下来放到Postman里试了一下果然能返回数据。我心想这下简单了只要循环请求这个接口把所有页的数据都爬下来就行了。结果我还是太年轻了。抓包一时爽参数坑你没商量我把Postman里的参数复制到代码里写了个循环从page1到page10准备一口气爬100个商品。结果第一页正常返回了第二页直接给我返回了个403 Forbidden。我靠什么情况我以为是请求太快了加了个time.sleep(3)再跑还是403。我又换了个代理IP还是403。我回到浏览器刷新了一下第二页能正常加载啊为什么代码里就不行然后我对比了一下浏览器里的第二页请求和我代码里的请求发现参数不一样。有几个参数是动态变化的_ksTS、callback、sign。尤其是那个sign参数每次请求都不一样而且看起来是一串32位的MD5值。我当时就头大了这他妈是参数加密啊最烦的就是这个没有之一。我之前帮一个客户爬京东的数据那个sign参数是用RSA加密的我花了三天才搞定最后客户给了我500块我觉得我亏到姥姥家了平均一小时才7块钱比外卖员都便宜。没办法只能硬着头皮上了。首先得找到这个sign参数是在哪里生成的。我在Sources标签里搜索“sign”搜出来几十个JS文件一个个点开看。大部分都是混淆后的代码变量名全是a、b、c、d看的我眼睛都花了喝了三杯咖啡才撑下来。我找了快两个小时终于在一个叫index.xxxx.js的文件里找到了生成sign的函数。格式化代码之后大概是这样的functiongenerateSign(params,appSecret){// 把参数按字典序排序letsortedKeysObject.keys(params).sort();letstr;for(letkeyofsortedKeys){strkeyparams[key];}// 加上appSecretstrappSecret;// MD5加密returnmd5(str);}我当时差点跳起来居然是这么简单的MD5加密我还以为是什么高大上的加密算法呢白担心了半天。然后我又找了一下那个appSecret是个固定值“1234567890abcdef”当然真实的不是这个我随便写的你们自己去扣。然后我用Python写了个生成sign的函数试了一下生成的和浏览器里的一模一样我当时那个开心啊感觉胜利就在眼前了。然后我把代码改了每次请求之前动态生成sign、_ksTS和callback参数。运行第一页正常第二页正常第三页…又403了。我靠怎么回事我又对比了一下参数发现还有个cookie参数里面有个tb_token字段是动态生成的有效期只有1小时。我之前复制的cookie里的tb_token已经过期了我当时就骂了一句脏话淘宝你有病吧token有效期搞这么短干嘛没办法只能重新登录复制新的cookie。然后我又发现这个接口还有请求频率限制一分钟最多只能请求5次超过了就会被封IP。我之前没注意一分钟请求了15次结果我的IP被封了手机上淘宝都打不开了吓得我赶紧关了程序等了一个小时才恢复。我当时真的想把电脑砸了就为了爬个破商品数据折腾了整整一天。后来我实在受不了淘宝的反爬了换了个目标苏宁易购。苏宁的搜索接口简直是新手的天堂没有复杂的sign参数只有一个简单的时间戳参数而且反爬也比较松。苏宁的搜索接口是https://search.suning.com/emall/searchV1Product.do参数也很简单keyword搜索关键词pageIndex页码从0开始pageSize每页商品数量最多20个_时间戳防止缓存就这么几个参数没有任何加密简直太良心了。我用了10分钟就写好了代码跑了一下完美所有商品数据都能正常返回而且没有被封IP。我当时差点感动哭了还是苏宁好啊。别以为拿到接口就万事大吉了反爬在后面等着你经过前面的折腾我终于能稳定地爬取苏宁的数据了。但是新的问题又来了。爬了大概20页的时候接口突然返回了一个空的商品列表。我以为是IP又被封了换了个代理还是空的。我回到浏览器翻到第20页能正常看到商品啊。然后我仔细看了一下响应发现里面有个提示“请登录后继续查看更多商品”。我靠原来苏宁的搜索结果只允许未登录用户看前20页登录之后才能看更多。我真是服了怎么每个网站都有这种破规定没办法只能模拟登录了。模拟登录有两种方法一种是手动输入账号密码但是苏宁有滑块验证码很难搞另一种是扫码登录这个比较简单。我用selenium写了个扫码登录的代码fromseleniumimportwebdriverfromselenium.webdriver.chrome.serviceimportServicefromwebdriver_manager.chromeimportChromeDriverManagerimporttimeimportjson# 自动下载对应版本的ChromeDriver不用自己手动下了太方便了driverwebdriver.Chrome(serviceService(ChromeDriverManager().install()))driver.get(https://passport.suning.com/ids/login)# 等待扫码登录给你30秒时间不够自己改time.sleep(30)# 获取登录后的cookiecookiesdriver.get_cookies()# 保存到文件下次直接用不用再扫码了withopen(suning_cookies.json,w)asf:json.dump(cookies,f)driver.quit()print(登录成功cookie已保存到suning_cookies.json)运行之后会弹出一个Chrome窗口显示苏宁的登录二维码用手机苏宁APP扫码登录之后等待30秒cookie就会保存到suning_cookies.json文件里。然后在爬虫代码里加载这个cookieimportrequestsimportjson sessionrequests.Session()# 加载登录后的cookiewithopen(suning_cookies.json,r)asf:cookiesjson.load(f)forcookieincookies:session.cookies.set(cookie[name],cookie[value])这样就可以用登录后的状态请求接口了能爬取所有的页面。然后我又加了一些反反爬措施随机延迟每次请求之间随机睡1-3秒避免被检测到轮换User-Agent用fake_useragent库随机生成不同的User-Agent异常处理如果请求失败就重试3次还是失败就跳过这一页数据保存每爬10页就保存一次数据防止程序崩溃导致数据丢失终于代码能稳定地跑了。我从晚上10点开始跑跑到凌晨1点半终于爬完了top500的商品数据一共25页500个商品。然后我把数据保存到Excel里发给了老王。老王说“威哥你太牛逼了明天我请你吃烧烤烤腰子管够”我说“滚蛋今天的烧烤先补上还有下次再让我爬淘宝我直接拉黑你。”完整代码给你们直接复制就能跑前提是你先运行上面的登录代码获取cookie# 安装依赖pip install requests beautifulsoup4 openpyxl selenium webdriver-manager fake-useragent# 要是装不上就加清华源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名importrequestsimportjsonimporttimeimportrandomfromopenpyxlimportWorkbookfromfake_useragentimportUserAgent# 初始化UserAgent随机生成浏览器标识uaUserAgent()# 创建Excel工作簿wbWorkbook()wswb.active ws.title苏宁商品数据# 写表头ws.cell(row1,column1,value商品标题)ws.cell(row1,column2,value价格)ws.cell(row1,column3,value销量)ws.cell(row1,column4,value店铺名)ws.cell(row1,column5,value商品链接)# 当前行号从2开始current_row2# 搜索关键词自己改keyword厨房置物架# 要爬的总页数自己改登录后最多能爬100页total_page25# 创建session保持cookiesessionrequests.Session()try:# 加载登录后的cookie一定要先运行登录代码生成这个文件withopen(suning_cookies.json,r)asf:cookiesjson.load(f)forcookieincookies:session.cookies.set(cookie[name],cookie[value])print(cookie加载成功)forpageinrange(total_page):print(f正在爬取第{page1}页...)# 构造请求参数params{keyword:keyword,pageIndex:page,pageSize:20,_:int(time.time()*1000)# 时间戳防止缓存}# 随机生成User-Agentheaders{User-Agent:ua.random,Referer:fhttps://search.suning.com/{keyword}/,Accept-Language:zh-CN,zh;q0.9}# 重试3次retry_count3whileretry_count0:try:responsesession.get(https://search.suning.com/emall/searchV1Product.do,paramsparams,headersheaders,timeout10)response.raise_for_status()# 如果状态码不是200抛出异常dataresponse.json()breakexceptExceptionase:retry_count-1print(f请求失败剩余重试次数{retry_count}错误信息{e})time.sleep(2)ifretry_count0:print(f第{page1}页请求失败跳过)continue# 解析商品数据goods_listdata.get(goodsList,[])ifnotgoods_list:print(f第{page1}页没有商品跳过)continueforgoodsingoods_list:try:titlegoods.get(commodityName,).strip()pricegoods.get(price,)salesgoods.get(saleCount,)shop_namegoods.get(shopName,).strip()# 构造商品链接goods_idgoods.get(commodityCode,)shop_idgoods.get(shopCode,)goods_urlfhttps://product.suning.com/{shop_id}/{goods_id}.html# 写入Excelws.cell(rowcurrent_row,column1,valuetitle)ws.cell(rowcurrent_row,column2,valueprice)ws.cell(rowcurrent_row,column3,valuesales)ws.cell(rowcurrent_row,column4,valueshop_name)ws.cell(rowcurrent_row,column5,valuegoods_url)current_row1exceptExceptionase:print(f解析商品失败错误信息{e})continue# 每爬10页保存一次防止程序崩溃数据丢失if(page1)%100:wb.save(suning_goods_partial.xlsx)print(f已保存前{page1}页数据)# 随机延迟1-3秒做个有素质的爬虫time.sleep(random.uniform(1,3))# 保存最终数据wb.save(suning_goods.xlsx)print(f爬取完成共爬取{current_row-2}个商品已保存到suning_goods.xlsx)exceptExceptionase:print(f程序出错了{e})# 出错了也要保存一下已经爬取的数据别白跑wb.save(suning_goods_error.xlsx)print(已保存部分数据到suning_goods_error.xlsx)对了要是你们实在搞不定那些加密参数就直接用selenium吧虽然慢是慢了点但是能解决90%的问题。给你们个selenium的简化版代码不用管什么接口什么参数直接爬页面上的元素就行fromseleniumimportwebdriverfromselenium.webdriver.chrome.serviceimportServicefromwebdriver_manager.chromeimportChromeDriverManagerfromselenium.webdriver.common.byimportByimporttimeimportrandomfromopenpyxlimportWorkbook driverwebdriver.Chrome(serviceService(ChromeDriverManager().install()))driver.get(https://search.suning.com/厨房置物架/)# 等待页面加载time.sleep(5)wbWorkbook()wswb.active ws.title苏宁商品数据ws.cell(row1,column1,value商品标题)ws.cell(row1,column2,value价格)current_row2# 爬5页forpageinrange(5):print(f正在爬取第{page1}页...)# 滚动到页面底部加载所有商品driver.execute_script(window.scrollTo(0, document.body.scrollHeight))time.sleep(2)# 获取所有商品goodsdriver.find_elements(By.CLASS_NAME,product-wrap)forgoodingoods:try:titlegood.find_element(By.CLASS_NAME,title-selling-point).text.strip()pricegood.find_element(By.CLASS_NAME,def-price).text.strip()ws.cell(rowcurrent_row,column1,valuetitle)ws.cell(rowcurrent_row,column2,valueprice)current_row1exceptExceptionase:print(f解析商品失败{e})continue# 点击下一页try:next_btndriver.find_element(By.LINK_TEXT,下一页)next_btn.click()time.sleep(random.uniform(2,4))exceptExceptionase:print(f没有下一页了{e})breakwb.save(suning_goods_selenium.xlsx)driver.quit()print(爬取完成)这个代码不用管什么Ajax什么加密只要页面上能看到的它都能爬下来非常适合新手。就是速度慢一点而且如果网站改版了元素的class名变了就得改代码。最后提醒你们一句爬虫有风险爬取需谨慎。别爬人家的敏感数据也别把人家的服务器搞崩了不然进去了可别怪我没提醒你。我昨天晚上睡觉的时候还梦见苏宁的法务给我发律师函吓死我了。今天就写到这吧我得去催老王请我吃烤腰子了。