python爬虫学习 - 查看显卡价格

这是一个简单的爬虫项目,用于从中关村网站上爬取显卡报价数据,后续可以考虑爬取相关的参数信息让数据更立体。数据的保存使用的是json,以python为主要开发语言。这个项目纯粹玩玩儿,不会使用什么现有的框架,只要能动就好。

python版本:3.6.8

pip版本:18.1(不想用21版本的,装一个库弹一个错误简直要命)

我的主要目的是要每日爬取NVIDIA显卡的均价和极值(看看那帮挖矿的什么时候死,写个装机单1w的成本里能有6k是花在卡上也是绝了),所以我需要对几个主要的显卡型号数据进行累加,保存到json当中,再通过其他方式输出出来。参照runoob所示的爬虫架构,整个爬虫的业务流程大致如下:

  • 构建待爬取页面列表list(即链接列表,一个链接指向一个查询页面)
  • 根据keyword遍历list下载网页并处理网页内容:
    • 使用正则表达式匹配目标段落,去除冗余元素
    • 从段落中提取价格(可通过修改表达式将价格和相关的参数表一并down下来)
    • 数据处理,我需要求均值所以将数据全部累加了起来
    • 遍历完一个keyword后,将数据写入json
  • 遍历完成后,保存到json文件中

一、 确认要爬取的页面链接

本次爬取的是中关村在线的搜索页面信息:https://detail.zol.com.cn/index.php?c=SearchList&keyword=1660&page=1

从链接格式中可以看出,目标网站是个php页面且带有查询参数,参数keyword对应的是查询字段,参数page对应的是分页页码,故爬取数据的时候可以通过设置这两个参数信息来调整爬取的方向。
利用f-string(格式化字符串常量,formatted string literals)构建链接列表,通过遍历列表抓取页面:

# 待查询显卡型号
keywords = [ "1060", "1660", "1070", "1080", "2060", "2070", "2080", "3060", "3060ti", "3070", "3070ti", "3080" ]
results = [] # 保存爬虫结果的json列表for keyword in keywords:# 根据keyword构建链接列表urls = [ f"https://detail.zol.com.cn/index.php?c=SearchList&subcateId=6&keyword={keyword}&page={i}" for i in range(1, 5) ]for url in urls:result = traverse(urls, keyword)    # 遍历当前链接列表results.append(result.to_json())  # 保存爬虫结果

traverse是一个用于遍历链接list的一个方法,后面会提到具体的实现。遍历的结果在命名后保存到results中作为一次遍历的结果。这里之所以会有两个循环是因为每个型号我都需要爬取前四页的价格信息,所以第一层循环遍历的是显卡型号列表,第二层才是开始遍历链接列表,注意区分。

上述内容在架构中发挥的是URL管理器的职能,管理所有待爬取的页面,并对已经访问过的内容进行标记以避免重复爬取。

其实在这个的基础上我们可以再进一步,扩展这一功能,将链接和参数列表作为input导入到一个指定的URL管理器类或者function中,由程序自动拼装并生成链接表,这些工作可以由f-string或者是正则来完成。

二、下载页面

我们需要定义一个方法用于下载页面信息,为下一步分析页面做好准备。理想情况是,主程序会给出一个url参数调用此方法,方法以字符串的形式返回一个纯文本html,程序可以直接对下载结果进行字符匹配。因此我们需要导入requests库,用get方法发起http请求并获取页面信息,页面的header信息和cookie可以通过F12获取。:

def downloadHtml(url):# 设置要传入的网页cookie,直接F12复制过来就好cookie = {"v": "1649838500","vn": "1S","Hm_lvt_ae5edc2bc4fc71370807f6187f0a2dd0": "1649766000","visited_serachKw": "3060","Adshow": "2","Hm_lpvt_ae5edc2bc4fc71370807f6187f0a2dd0": "1665768000","questionnaire_pv": "1649766000","visited_subcateId": "3 | 13","visited_subcateProId": "3-0 | 13-0"}headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36"}res = requests.get(url=url, headers=headers, timeout=5, cookies=cookie)res.encoding = "GB2312"return res.text

这里重点解释下为什么要cookie,这其实是一种反制反爬虫策略中的一个常见方案。

有些网站为了减轻爬虫频繁访问给服务器带来的压力会设置反爬虫策略(比如京东),对于一些缺少cookie或者不是从本站发起的访问请求,网站只会返回一个空白页面。具体的禁止访问协议可以通过查看网站的robots.txt文档来查看,比如中关村的就是 https://detail.zol.com.cn/robots.txt。

所以要绕开反爬虫策略,我们就必须构建一个cookie和header,在发起get请求时作为参数传入方法中,就可以正常的获取页面了。反制反爬虫的策略有很多,有兴趣的可以具体研究,这里不再赘述。

header:页面请求头,点击F12后在network一栏找到对应的链接,点击进去后再最下方可以找到header信息,一般是通用的,可以直接复制案例里的那行。如果使用的是requests.post方法,则一定要传入header。

cookie:临时缓存,点击F12后在Applications一栏里面可以找到。

三、定义查询结果类

这一步并非必须,只是从个人习惯上出发,我喜欢将查询结果合并成一个具体的实例化类对象而非一个集合,通过类对象我可以进一步构建对象列表并定义一些方法来辅助操作。尽管Python本质上鼓励的是面向函数开发而非面向对象开发,我仍旧可以通过定义类的形式来实现我的目的:

# ResultItem.py
# 查询结果类,保存被查询对象的名称、均价、统计数量、最高价与最低价信息class ResultItem:def __init__(self, name="", sum=0, counts=0, max_price=0, min_price=99999):"""构造函数Args:name:       查询对象名称sum:        统计金额总和counts:     统计次数总和avg:        查询对象的均价max_price:  查询对象的最高价min_price:  查询对象的最低价"""self.name = nameself.sum = sumself.counts = countsself.max_price = max_priceself.min_price = min_priceself.avg = 0def updateByValue(self, sum, counts, value):"""通过单个价格更新调用对象的最高价或最低价"""self.sum = sumself.counts = countsself.set_price(value)def update(self, sum, counts, max_price, min_price):"""更新调用对象的属性值"""self.sum = sumself.counts = countsself.set_max_price(max_price)self.set_min_price(min_price)self.avg = self.sum / self.countsdef set_price(self, value):"""比较输入值与调用对象的最高价/最低价之间的大小并在发现大于/小于调用对象的最高价/最低价时进行数值交换"""self.set_max_price(value)self.set_min_price(value)def set_max_price(self, value):"""设置最高价,当输入值小于调用对象的最高价时,调用对象的最高价值不会被改变Args:value: 输入值"""self.max_price = self.max_price if self.max_price > value else valuedef set_min_price(self, value):"""设置最低价,当输入值大于调用对象的最低价时,调用对象的最低价值不会被改变Args:value: 输入值"""self.min_price = self.min_price if self.min_price < value else valuedef to_string(self):return f"[name: {self.name}, avg: {self.avg}, counts: {self.counts}, max_price: {self.max_price}, min_price: {self.min_price}]"def to_json(self):return {"name": self.name,"sum": self.sum,"avg": self.avg,"counts": self.counts,"max_price": self.max_price,"min_price": self.min_price}

我再次重申这点不是必须的,单纯学习爬虫的话不需要特地定义查询结果类,这只是一个个人习惯。但我鼓励大家将参数或查询结果以类对象的形式传入/传出(尤其在多个方法返回同一格式的返回值集合时),类的操作性和语言含义要比单纯的set或map更加丰富,并且可以根据业务需求自定义规则,熟悉SSM框架的同志应该深有体会。

四、遍历链接列表并爬取数据

在前面我们定义了一个traverse方法用于遍历urls,现在就是实现它的时候了。这里我传入了两个参数,一个是urls,也就是链接列表,另一个是keyword,用来标记这个列表查询的是哪一种显卡型号:

def traverse(urls, keyword):item = ResultItem(keyword)for url in urls:print("正在抓取:", url)# 下载页面信息并提取数据page_result = analysis(downloadHtml(url))# 更新遍历结果的数据item.update(item.sum + page_result.sum,item.counts + page_result.counts,page_result.max_price,page_result.min_price)return item

这部分的思路很简单:遍历列表 => 下载html => 提取数据 => 保存数据,提取的部分我单独抽出来写了个analysis方法,直接传入downloadHtml的结果,方法会返回一个查询结果类对象page_result:

def analysis(html):# import retext_pattern = re.compile('<b class="price-type">.*?</b>')num_pattern = re.compile('[1-9][0-9]{0,}')item = ResultItem()price_box = text_pattern.findall(html)for price_type in price_box:price = num_pattern.findall(price_type)if(len(price) > 0):value = int(price[0])  # 按这个匹配模式出来的数字只可能有一个,直接调用即可item.updateByValue(item.sum + value, item.counts + 1, value)return item

通过观察页面我们可以发现,所有的价格都是包裹在'<b class="price-type">.*?</b>'里面的,所以我们可以很容易的通过正则表达式将它们全部提取出来,再逐一遍历找出其中的数字并累加到查询结果item中。

到了这一步,最关键的就是要能用正则准确的找到我们想要的部分,所以一定要多次模拟测试,可以自己从页面上拷贝整个html到runoob的在线页面上测试正则表达式的准确性与合理性,这会影响到数据的真实性与合理性。

五、保存查询结果

最后一步是保存,我已经有了一个results以json列表的形式保存了所有的查询结果,现在只要写入json文件就好,简单点儿做的话可以直接导入json库用现成的方法。加上save_json方法后,主文件现在的内容是这样的:

import requests
import re
import json
import time
from ResultItem import ResultItemdef main():# 不保留session,防止因为大量访问导致“Max retries exceeded with url”一类的问题出现requests.session().keep_alive = Falsestart = time.time()keywords = ["1060", "1660", "1070", "1080", "2060", "2070", "2080", "3060", "3060ti", "3070", "3070ti", "3080"]results = []# 页面链接列表for keyword in keywords:# 构建链接列表urls = [f"https://detail.zol.com.cn/index.php?c=SearchList&subcateId=6&keyword={keyword}&page={i}"for i in range(1, 5)]result = traverse(urls, keyword)results.append(result.to_json())save_json(results)end = time.time()print(f"运行时长:{end - start}")read()def traverse(urls, keyword):item = ResultItem(keyword)for url in urls:print("正在抓取:", url)# 导入关键词和页面htmlpage_result = analysis(downloadHtml(url))item.update(item.sum + page_result.sum,item.counts + page_result.counts,page_result.max_price,page_result.min_price)return itemdef analysis(html):text_pattern = re.compile('<b class="price-type">.*?</b>')num_pattern = re.compile('[1-9][0-9]{0,}')item = ResultItem()price_box = text_pattern.findall(html)for price_type in price_box:price = num_pattern.findall(price_type)if(len(price) > 0):value = int(price[0])  # 按这个匹配模式出来的数字只可能有一个,直接调用即可item.updateByValue(item.sum + value, item.counts + 1, value)return itemdef save_json(results):with open("record.json", "w") as f:json.dump(results, f)print("已将结果保存至json")returndef downloadHtml(url):# 设置要传入的网页cookiecookie = {"v": "1649838500","vn": "1S","Hm_lvt_ae5edc2bc4fc71370807f6187f0a2dd0": "1649766000","visited_serachKw": "3060","Adshow": "2","Hm_lpvt_ae5edc2bc4fc71370807f6187f0a2dd0": "1665768000","questionnaire_pv": "1649766000","visited_subcateId": "3 | 13","visited_subcateProId": "3-0 | 13-0"}headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36"}res = requests.get(url=url, headers=headers, timeout=5, cookies=cookie)res.encoding = "GB2312"return res.textdef read():"""简单的读取方法,把json数据读取并打印出来看看效果"""with open("record.json", "r") as f:list = json.load(f)for index in list:print(f"型号:{index['name']}\t均价:{index['avg']:.2f}\t最高价:{index['max_price']}\t最低价:{index['min_price']}")returnif __name__ == '__main__':main()

如此,我们就将数据全部爬了下来并保存到了我们自己的数据文件中,使用json有个好处就在于我们可以非常简单直观的看到数据的情况。当然这次只保存了一次的数据,如果要做到每日追踪可以考虑些bat文件每天定时运行,或者自己手动跑,两种方案都行。

附录

这里记录了整个过程中参考的网站和资料,感谢dalao们的分享与贡献。

[1] 10行代码集2000张美女图,Python爬虫120例,再上征途
[2] Python typeerror: list indices must be integers or slices, not str Solution
[3] python读写json文件
[4] Python爬虫实战入门四:使用Cookie模拟登录——获取电子书下载链接
[5] python 关于Max retries exceeded with url 的错误
[6] Requests: 让 HTTP 服务人类
[7] Python使用format与f-string数字格式化

python爬虫学习 - 查看显卡价格相关推荐

  1. Python爬虫学习手册

    like:128-Python 爬取落网音乐 like:127-[图文详解]python爬虫实战--5分钟做个图片自动下载器 like:97-用Python写一个简单的微博爬虫 like:87-爬虫抓 ...

  2. Python 爬虫学习 系列教程

    Python爬虫 --- 中高级爬虫学习路线 :https://www.cnblogs.com/Eeyhan/p/14148832.html 看不清图时,可以把图片保存到本地在打开查看... Pyth ...

  3. 从入门到入土:Python爬虫学习|实例练手|详细讲解|爬取腾讯招聘网|一步一步分析|异步加载|初级难度反扒处理|寻找消失的API来找工作吧

    此博客仅用于记录个人学习进度,学识浅薄,若有错误观点欢迎评论区指出.欢迎各位前来交流.(部分材料来源网络,若有侵权,立即删除) 本人博客所有文章纯属学习之用,不涉及商业利益.不合适引用,自当删除! 若 ...

  4. 从入门到入土:Python爬虫学习|Selenium自动化模块学习|简单入门|轻松上手|自动操作浏览器进行处理|chrome|PART01

    此博客仅用于记录个人学习进度,学识浅薄,若有错误观点欢迎评论区指出.欢迎各位前来交流.(部分材料来源网络,若有侵权,立即删除) 本人博客所有文章纯属学习之用,不涉及商业利益.不合适引用,自当删除! 若 ...

  5. Python爬虫学习框架介绍

    对于初学者来说,摸索清楚一个领域的知识体系往往比单纯学习某个技术要重要得多,因为技术总会跟随时代发生快速变化,而知识体系往往变化较小,今天我们以自学的角度来了解一下Python爬虫的知识体系吧. 一. ...

  6. Python爬虫学习实战

    Python爬虫学习实战 前期回顾 概述 技术要求 实战 网页分析与数据提取 小说目录提取 小说章节内容 总结 前期回顾 Python爬虫学习之requests Python爬虫学习之数据提取(XPa ...

  7. Python爬虫学习①:

    Python爬虫学习①: 前言:本文系根据唐松<Python网络爬虫从入门到实践>书籍进行学习和整理 Python 爬虫的流程分为了三部分 ①:获取网页:给网址发送一个请求,该网址返回整个 ...

  8. Python爬虫学习笔记 -- 爬取糗事百科

    Python爬虫学习笔记 -- 爬取糗事百科 代码存放地址: https://github.com/xyls2011/python/tree/master/qiushibaike 爬取网址:https ...

  9. Python爬虫学习 6 —— 使用bs4库爬取大学排名

    前面学了如何使用beautifulsoup,现在来尝试简单的爬取:中国大学排名 一.准备 查看Robots协议:robots协议 功能描述 输入:大学排名的url链接 输出:大学排名信息(排名,大学名 ...

最新文章

  1. 图神经网络:方法与应用 | 一文展望,四大待解问题
  2. 阿里云供应链大赛-榜单
  3. C语言学习之用*打印菱形
  4. IOS开发之JSON文件的读写
  5. linux的kerne启动过程,linux
  6. 加密Python脚本
  7. sql语句循环截取字符串
  8. 升级ubuntu后EMACS 无法使用
  9. andorid自定义ViewPager之——子ViewPager滑到边缘后直接滑动父ViewPager
  10. java研磨设计模式_研磨设计模式之单例模式(内部类)
  11. 使用fopen/fwrite/fread/fseek/fclose对文件从头读写整型数
  12. 基于QT的推箱子小游戏设计
  13. word里边页眉下面的下划线怎么去掉
  14. 一些EXCHANGE命令
  15. pvs-stdio ue4_云中的PVS-Studio:Azure DevOps
  16. 数据库简介、SQL 语的增加删除修改查询命令
  17. 如何将数据导入python
  18. 以下python语言关键字在异常处理_python后端开发工程师考证试题
  19. 微信小程序-编辑器插件
  20. 世界各地的游戏都是如何分级的?哪个最严格?

热门文章

  1. IIS中FTP登陆用户名密码都对但进不去的另一种原因
  2. IIC模拟协议华大单片机移植
  3. 前端加密php后端解密,使用RSA怎么实现JavaScript前端加密与PHP后端解密功能
  4. R4.0.2版本Rtools4安装教程(2023-02-14)
  5. 查询海康、大华RTSP协议
  6. 三步走做好艾默生质量流量计使用工作
  7. 电子传真虚拟化的优势
  8. 产品经理的10大困惑
  9. 【C语言】平均分的战争。有m个人的成绩存放在score数组中,请编写程序求出平均成绩并且罗列出低于平均分的分数。
  10. C实现Unix时间戳和本地时间转化