【超详细】使用python3做一个爬虫,监控网站信息(一)

由于朋友需要监视几个网页,来获取网页的更新信息。之前使用人工刷新的方法,不仅耗时耗力,效率低,而且时效性很差。于是委托我做一个程序,可以监控这几个网页的更新信息,如果页面更新了东西的话,可以直接通过邮件/微信发送给他。作为一个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  点击筛选条件,类型为销售,公告类型为项目公告,属性和市场选全部。如图,我们要获取的是这个页面的更新信息。

image.png

  

一、思路分析

1. 那么如何获取这个页面的更新信息呢?其实简单得来讲,只要循环对比栏目中的第一条信息,如果获取到的第一条信息与上一次获取到的第一条信息不一样,那么可以判定更新了,这个时候通过邮件,把更新的信息发送给用户就可以了。虽然这几个网站虽然地址不一样,但是网页结构是一模一样的,只要拿其中一个页面分析,直接套用到其他网页就好了。

image.png

 

画一下流程图:

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浏览器,打开这个页面,我们看到筛选条件是默认的:

image.png

然后我们依次点击 销售,项目公告

image.png

发现浏览器的地址根本就没有改变。那么如何确定在这个筛选条件下,这个页面的唯一性呢?

我们可以用抓包工具,抓取点下链接后浏览器向服务器的请求地址。chrome浏览器自带抓包工具,如图,按F12点击network

image.png

然后我们再依次点击 销售-项目公告

network里面更新了抓到的包,如图,我们查看第一个文件的header

image.png

这里清楚地显示了文件的请求地址是“http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1&keyword=&inforCode=&time0=&time1=”,我们新开一个窗口,把这个请求地址粘贴进去:

image.png

如上图,虽然连接还是跳转回到了原来的连接,但是下面的筛选条件却改变了,我们找到了这个筛选条件下页面的唯一地址。

再来分析一下这个唯一地址(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 分析需要采集的数据

如图,我们需要采集的是最新一条信息的链接标题和链接地址。在这个链接上右键-检查。可以看到完整的信息:

image.png

这是一个 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 运行一下,可以看到打印出了整个网页内容:

image.png

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对代码的缩进有严格的要求,不能删掉上面的缩进

保存运行一下,可以看到,所有的链接地址和文字都打印出来了:

image.png

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里面看一下,域名后面字符的长度:

image.png

长度为50,那么如果把完整的地址(url变量)当成一个列表,从倒数第51个字符串开始,反向截取,看看是什么结果。

image.png

可以看到,用url[:-50]就可以返回该网站的域名,那么上面的代码打印输出那两行我们再修改下:

    print(info_text)
    print(url[:-50]+info_link+'n')

image.png

保存运行,查看下结果:

image.png

成功加上了域名的前缀。

4.获取页面的第一条信息的地址和标题

上面我们已经可以采集这个页面所有的链接地址了。可是我们只需要第一条公告中的链接。BeautifulSoup模块中的find_all()函数提供了限制采集的功能,我们来尝试一下。

将上面的 for link in html.find_all('a'): 改为 for link in html.find_all('a',limit=5):

image.png

重新运行下查看结果:

image.png

打印出了前5个链接,我们需要的是第3个链接

把 limit的值改为3,再次运行:

image.png

怎么把前面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')

运行一下:

image.png

成功采集到这条地址。

四、批量采集所有页面中的第一个链接

如果要采集上面十个页面的各自第一个链接,需要定义一个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')

运行一下,结果如下:

image.png

不小心写多了……未完待续

评论

目前评论:0   

点击加载更多评