由于朋友需要监视几个网页,来获取网页的更新信息。之前使用人工刷新的方法,不仅耗时耗力,效率低,而且时效性很差。于是委托我做一个程序,可以监控这几个网页的更新信息,如果页面更新了东西的话,可以直接通过邮件/微信发送给他。作为一个python还未入门的选手,对我而言这是个不小的挑战,首先感谢@wkm(博客:https://www.xiaoweigod.cn),node大佬的倾情帮助,对于程序的逻辑改进给出了惊为天人的方案。
这里我把这个python爬虫的写法详细地写成博文,以备日后参考,也方便给萌新们学习一下,也请大佬们高抬贵手。当然更希望大佬们可以提出一些指点意见,非常感谢!
首先介绍下这几个网站:
http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
http://wz.cd-rail.com/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.cd-rail.com/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
http://wz.xian.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.xian.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
http://wz.huhht.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.huhht.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
http://wz.qingz.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.qingz.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
比如我们打开第一个链接, http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1 点击筛选条件,类型为销售,公告类型为项目公告,属性和市场选全部。如图,我们要获取的是这个页面的更新信息。
一、思路分析
1. 那么如何获取这个页面的更新信息呢?其实简单得来讲,只要循环对比栏目中的第一条信息,如果获取到的第一条信息与上一次获取到的第一条信息不一样,那么可以判定更新了,这个时候通过邮件,把更新的信息发送给用户就可以了。虽然这几个网站虽然地址不一样,但是网页结构是一模一样的,只要拿其中一个页面分析,直接套用到其他网页就好了。
画一下流程图:
2. 如何利用python3来实现
网页获取可以用python3的urllib模块,网页分析可以用BeautifulSoup模块,邮件发送可以用smtplib和mail模块。
通过urllib模块下载整个网页,然后使用BeautufulSoup分析并提取有用的信息,最后使用mail模块,利用smtp给用户发送更新邮件。
二、分析web页面
在写爬虫前,必须对需要爬取的页面进行分析,找出需要爬取的信息,然后罗列出来,一一在程序里实现。下面以第一个任务(http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部)为例,介绍如何分析web页面。
1 分析页面的链接地址规则。
使用 chrome浏览器,打开这个页面,我们看到筛选条件是默认的:
然后我们依次点击 销售,项目公告:
发现浏览器的地址根本就没有改变。那么如何确定在这个筛选条件下,这个页面的唯一性呢?
我们可以用抓包工具,抓取点下链接后浏览器向服务器的请求地址。chrome浏览器自带抓包工具,如图,按F12点击network。
然后我们再依次点击 销售-项目公告。
network里面更新了抓到的包,如图,我们查看第一个文件的header:
这里清楚地显示了文件的请求地址是“http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1&keyword=&inforCode=&time0=&time1=”,我们新开一个窗口,把这个请求地址粘贴进去:
如上图,虽然连接还是跳转回到了原来的连接,但是下面的筛选条件却改变了,我们找到了这个筛选条件下页面的唯一地址。
再来分析一下这个唯一地址(http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1&keyword=&inforCode=&time0=&time1=),发现里面的keyword、inforCode、time、time这些参数都没有指定值,可以直接去掉,因此可以将这个链接改为:http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1
粘贴到浏览器,看一下,没有问题。
2 分析需要采集的数据
如图,我们需要采集的是最新一条信息的链接标题和链接地址。在这个链接上右键-检查。可以看到完整的信息:
这是一个 a 标签,“href”后面包含的是链接的地址(mainPageNotice.do?method=info&id=OJ002018051602811725%40OJ002018051602811726%4030),其他则是文字标题([线下][竞卖]5月25日废旧物资网上竞价销售公告)。
三、用python3尝试采集这个链接
这里我用centos7,安装python3环境,安装方法请看:
centos下安装python3并与自带的python2共存
需要用到python3的urllib模块和BeautifulSoup模块。运行以下命令安装:
pip install BeautifulSoup4
urllib模块自带的,因此无无需安装。
1 用urllib获取页面
用vim新建一个名为 get_update.py的python程序
vim get_update.py
获取http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1这个页面的代码如下:
#coding=utf-8 #调用urllib.request模块 import urllib.request #定义url地址 url='http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1 ' #定义发送的请求 req=urllib.request.Request(url) #将服务器返回的页面放入rsp变量 rsp=urllib.request.urlopen(req) #读取这个页面,并解码成utf-8格式,忽略错误,放入变量html中 html=rsp.read().decode('utf-8','ignore') #打印html里的内容 print(html)
保存,然后 python get_update.py 运行一下,可以看到打印出了整个网页内容:
2 用BeautifulSoup分析采集页面
上一步已经获取到了页面的内容,这一步我们通过BeautifulSoup来分析页面,并提取信息。
beautifulSoup的官方文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
通过官方文档的描述,我们可以使用BeautifulSoup里的find_all()函数,找出所有的a标签,然后用get()函数获取a标签的内容,使用get_text()函数获取文字。
继续添加如下代码:
#导入BeautifulSoup模块 from bs4 import BeautifulSoup #使用BeautifulSoup模块解析变量中的web内容 html=BeautifulSoup(html,'html.parser') #循环找出所有的a标签,并赋值给变量 link for link in html.find_all('a'): #把href中的内容赋值给info_link info_link=link.get('href') #把a标签中的文字赋值给info_text,并去除空格 info_text=link.get_text(strip=True) #打印出info_text和info_link,并换行 print(info_text) print(info_link+'n')
注意,python对代码的缩进有严格的要求,不能删掉上面的缩进。
保存运行一下,可以看到,所有的链接地址和文字都打印出来了:
3 补全链接
我们看到采集的链接,似乎只有一半。这是因为在html中,约定点击这种链接,前面自动加上网站的地址。比如点击 mainPageNotice.do?method=info&id=IJ002018051501758115%40IJ002018051501758050%4030 浏览器会理解为点击:http://wz.lanzh.95306.cn/mainPageNotice.do?method=info&id=IJ002018051501758115%40IJ002018051501758050%4030
但是我们采集出来给用户的信息不能这样,看起来有点没头没脑的。怎么才能补全链接呢?如果直接在输出的链接前加上前缀字符串(网站域名) 'http://wz.lanzh.95306.cn/' 的话,那么其他不同域名网站就没法采集了。
那么怎么根据网站的域名变化定义这个前缀的字符串(网站域名)呢?我们可以看到,url变量里面就包含了这个前缀字符串,我们可以把url这个变量当成python的列表处理,提取其中的域名。
根据第二节的1小节,分析链接地址,我们把需要采集的页面,全都转化为get请求的地址,既:
http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
http://wz.cd-rail.com/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.cd-rail.com/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
http://wz.xian.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.xian.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
http://wz.huhht.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.huhht.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
http://wz.qingz.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部
http://wz.qingz.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:中标公告 属性:全部 市场:全部
转化为:
http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1
http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1
http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2000001&cur=1
http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2200001&cur=1
http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1
http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1
http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1
http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1
http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1
http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1
观察一下这些转化后的地址,可以发现虽然域名一样,但域名后面的内容是一样的。
我们直接在python里面看一下,域名后面字符的长度:
长度为50,那么如果把完整的地址(url变量)当成一个列表,从倒数第51个字符串开始,反向截取,看看是什么结果。
可以看到,用url[:-50]就可以返回该网站的域名,那么上面的代码打印输出那两行我们再修改下:
print(info_text) print(url[:-50]+info_link+'n')
保存运行,查看下结果:
成功加上了域名的前缀。
4.获取页面的第一条信息的地址和标题
上面我们已经可以采集这个页面所有的链接地址了。可是我们只需要第一条公告中的链接。BeautifulSoup模块中的find_all()函数提供了限制采集的功能,我们来尝试一下。
将上面的 for link in html.find_all('a'): 改为 for link in html.find_all('a',limit=5):
重新运行下查看结果:
打印出了前5个链接,我们需要的是第3个链接。
把 limit的值改为3,再次运行:
怎么把前面2个链接去掉呢? 其实很简单,我们看这个for循环:
for link in html.find_all('a',limit=3): #把href中的内容赋值给info_link info_link=link.get('href') #把a标签中的文字赋值给info_text,并去除空格 info_text=link.get_text(strip=True) #打印出info_text和info_link,并换行 print(info_text) print(url[:-50]+info_link+'n')
每找到一个a标签,就给info_link和info_text赋值一次,并打印出来。因为赋值是会覆盖的,所以可以等整个for循环运行完毕后,再把info_link和info_text的值打印出来,就对应了最后一条链接(第三条)的内容。
再次更改程序,把print这两条的缩进去掉,移除for循环:
for link in html.find_all('a',limit=3): #把href中的内容赋值给info_link info_link=link.get('href') #把a标签中的文字赋值给info_text,并去除空格 info_text=link.get_text(strip=True) #打印出info_text和info_link,并换行 print(info_text) print(url[:-50]+info_link+'n')
运行一下:
成功采集到这条地址。
四、批量采集所有页面中的第一个链接
如果要采集上面十个页面的各自第一个链接,需要定义一个url的合集(列表),把所有需要采集的页面地址都包含进去:
url_list=['http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1']
然后用for循环把url全都地址打印出来,再把url传入我们的程序里,最后再打印出来。大致拓扑如下:
for url in url_list: urllib访问获取页面 ... for link in html.find_all('a',limit=3) BeautifulSoup处理提取a标签内容 ... print(info_text) print(url[:-50]+info_link+'n')
完整代码如下:
#coding=utf-8 #导入urllib.request模块 import urllib.request #导入BeautifulSoup模块 from bs4 import BeautifulSoup #把所有需要采集的页面地址都放入url_list这个列表 url_list=['http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1'] #第一层循环,把url都导出来 for url in url_list: #定义发送的请求 req=urllib.request.Request(url) #将服务器返回的页面放入rsp变量 rsp=urllib.request.urlopen(req) #读取这个页面,并解码成utf-8格式,忽略错误,放入变量html中 html=rsp.read().decode('utf-8','ignore') #使用BeautifulSoup模块解析变量中的web内容 html=BeautifulSoup(html,'html.parser') #第二层循环,找出所有的a标签,并赋值给变量 link for link in html.find_all('a',limit=3): #把href中的内容赋值给info_link info_link=link.get('href') #把a标签中的文字赋值给info_text,并去除空格 info_text=link.get_text(strip=True) #打印出info_text和info_link,并换行 print(info_text) print(url[:-50]+info_link+'n')
运行一下,结果如下:
不小心写多了……未完待续