NMT Data Mining & Big Data

爬中国政府采购网的案例分析

2019-05-18
NMt

爬取中国政府采购网的案例。

@@@@
  1. 查看数据,构建基本框架

    首先先打开网页看看我们需要爬什么样的数据:

    中国政府采购网

    往下滑就可以看到我们需要的数据是长什么样子的啦!

    我们要做的就是将所有数据以品目、类别为索引进行查找数据,并将其从网页上爬下存在本地数据库中。

  2. 观察链接,拼接出所有网址

    链接 含义
    http://search.ccgp.gov.cn/bxsearch?searchtype=1 主页面的网址,子页面的开头都是这样,不会变动
    &page_index=1 此参数是代表当前页数,从1开始
    &bidSort=0&buyerName=&projectId= 其他参数,在后续进行拼接网址时不进行更改
    &pinMu=1 品目的选项,1货物类;2工程类;3服务类
    &bidType=1 招标类型,1 –公开招标; 2–询价公告;….
    &dbselect=bidx&kw= 固定不变的
    &start_time=2015%3A01%3A01 这里是开始的时间
    &end_time=2015%3A12%3A31 这里是结束的时间
    &timeType=6 如果需要自定义开始结束时间,这里的参数填6
    &displayZone=&zoneId=&pppStatus=0&agentName= 固定不变的

    此时代码的大致框架就已经形成了,构造一个三层的嵌套循环,最内一层则是页数的变换,即是更改&page_index=后的数字,中间一层循环和最外层循环则是品目和类别。(位置可以颠倒)

  3. 访问网址,获取到网址内容

    接下来定义了一些关于拼接网址的参数:

    创建和数据库的连接,由于小编要将爬取到的数据存储在MySQL数据库中,所以需要提前创建连接,以便后续的存储:

    先将第二步的三层嵌套循环的框架搭好。紧接着完成最重要的一步:将要访问的网址拼接好。如下图所示,小编改变了“&page_index=”、“&pinMu=”、“&bidType=”、“&start_time=”以及“&end_time=”这五个参数。这5个参数分别是控制当前所在页、品目、公告类型、开始时间、结束时间等功能。拼接之后便用requests进行访问网页,BeatifulSoup美化源码结构。

  4. 解析获取的文本
    上述代码获取到的内容的部分截图如下:

    你可以看到在第一步中展示的待爬取信息现在已经转变为本地文本,由于抓取下的数据是整个网页的源码,里面含有大量我们并不需要的信息,因此小编接下来做的事就是提取自己需要的信息。例如标题、时间等信息都是小编需要的信息。下面详细讲解如何获取到标题,其他字段抓取都可以照猫画虎了。
    打开其中一个网址,按下F12,跟着下面箭头顺序点击;这时右侧的网页源码有一处会用蓝色高亮显示,这个就是对应刚刚点击标题的源码。

    打开a标签,会发现我们需要的标题内容就在这个标签下,仔细观察标签的位置,可以看到标题在一个class= vT-srch-result-list-bid的ul标签下,进一步是一个li标签,最后在li标签的a标签下。

    根据上述我们找到的关系,利用select函数即可获取到指定标签下的文本内容,代码如下: title = soup.select(‘.vT-srch-result-list-bid li > a’) 这时title里存储的是Tag类型的数据,无法看到其中的内容,用下述代码即可将title转化为文本: titles = [tle.text.strip() for tle in title]

    得到的内容如下所示:

    接下来就是获取自己想要的字段了。以下是我的代码,仅供参考,可以根据自己的需求来设计代码。

    还缺一个中止翻页的代码:

    这部分代码就是判断网页是否循环到了尾页。

  5. 存入数据
    这部分代码实现的功能就是连接外部数据库,将数据存储在数据库中。我用的MySQL数据库哦~

这两天收到写一篇关于分析这个案例文章的任务,花了一阵功夫写的,想着不能浪费了,就放在了自己的博客上。

完整代码如下:

# -*- coding: utf-8 -*-
"""
Created on Fri Mar 22 13:41:11 2019

@author: Nora
"""

from urllib import request
from bs4 import BeautifulSoup
import pymysql
import requests
import time 

start_list=['2015%3A01%3A01','2016%3A01%3A01','2017%3A01%3A01','2018%3A01%3A01']
end_list=['2015%3A12%3A31','2016%3A12%3A31','2017%3A12%3A31','2018%3A12%3A31']
time_data=['2015','2016','2017','2018']
pinmu_data = ['货物类','工程类','服务类']

page404 = """
<!-- Page Header -->
<header class="intro-header" style="background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}')">
	<div class="container">
		<div class="row">
			<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
				<div class="site-heading" id="tag-heading">
					<h1>404</h1>
					<span class="subheading">{{ page.description }}</span>
				</div>
			</div>
		</div>
	</div>
</header>

<script>
	document.body.classList.add('page-fullscreen');
</script>
"""

i = 0  ### 年份 2015-2018, 对应于0-3
dbconn=pymysql.connect(
      host="localhost",
      database="bid_notice_info",
      password="123456",
      user="root",
      port=3306,
      charset='utf8mb4'
     )  
import os
import hashlib

def md5Encode(img):
    m = hashlib.md5()
    m.update(img.encode("utf8"))
    return m.hexdigest()

for j in range(1,13):
    '''
    类型的循环,1-12 一共12种类型
    '''
    for k in range(1,4):
        '''
        品目的循环,1-3,一共3种品目
        '''
        a=1
        z=0
        
        titles = [1]
        cur=dbconn.cursor()
        while a:
            '''
            页数的循环,停止条件是判断下一页的链接是否为空
            '''
            try:
                
                url = 'http://search.ccgp.gov.cn/bxsearch?searchtype=1&page_index='+str(a)+\
                '&bidSort=0&buyerName=&projectId=&pinMu='+str(k)+'&bidType='+str(j)+'&dbselect=bidx&kw=&start_time='\
                +start_list[i]+'&end_time='+end_list[i]+'&timeType=6&displayZone=&zoneId=&pppStatus=0&agentName='
            
                # 获取网页部分
                content = requests.get(url).text
                soup = BeautifulSoup(content, 'html.parser')
            
                # 判断是否终止翻页循环
                if soup.select('.vT-srch-result-list-bid li > a')[-1].text.strip() == titles[-1]:
#                    a=0
                    if soup.select('.vT-srch-result-list-bid li > a')[-1].text.strip() == titles[-1]:
                        break
                    a += 1
                    continue
                if z!= 0:
                    print('==========报错已恢复=========')
                    z = 0
                # 初始化
                titles = []
                times = []
                pur_ps = []  # 采购人
                agencys = []  # 代理机构
                not_types = []  # 公告类型
                areas = []  # 地区
                clase = []  # 地区后面那一项
                
                # 解析网页部分,提取所要的字段
                title = soup.select('.vT-srch-result-list-bid li > a')
                titles = [tle.text.strip() for tle in title]
                cont = soup.select('.vT-srch-result-list-bid span')
#                cont = [''.join(ct.text.split()) for ct in cont]
                cont_1 = [ct.text.split('|') for ct in cont]
                for c in range(len(cont_1)):
                    t = 0
                    while t < len(cont_1[c]):
                        if cont_1[c][t].isspace():
                            cont_1[c].remove(cont_1[c][t])
                            t -= 1
                        t += 1
                times = [ct[0] for ct in cont_1]
                pur_ps = [ct[1] for ct in cont_1]
                for y in range(len(cont_1)):
                    try:
                        ag = cont_1[y][2].split()[0]
                        no = cont_1[y][2].split()[1]
                        ar = cont_1[y][3]
                        cl = cont_1[y][4]
                    except:
                        if len(cont_1[y]) == 6:
                            ag = cont_1[y][2]
                            no = cont_1[y][3]
                            ar = cont_1[y][4]
                            cl = cont_1[y][5]
                        else:
                            no = 'Null'
                            ar = 'Null'
                            cl = 'Null'
                    agencys.append(ag)
                    not_types.append(no)
                    areas.append(ar)
                    clase.append(cl)
#                agencys = [ct[2].split()[0] for ct in cont_1]
#                not_types = [ct[2].split()[1] for ct in cont_1]
#                try:
#                    areas = [ct[3] for ct in cont_1]
#                except:
#                    areas = ['Null'] * len(cont_1)
#                try:
#                    clase = [ct[4] for ct in cont_1]
#                except:
#                    clase = ['Null'] * len(cont_1)
                year = time_data[i]
                
                for r in range(len(title)):
                    '''
                    逐条存储到数据库
                    '''
                    times_nd=times[r].split(' ')[0].replace('.','-')
                    pur_ps_nd=pur_ps[r].split('\r')[0]
                    areas_nd=areas[r].split('\r')[0]
                    clase_nd=clase[r].replace('\n','')
                    get_url=title[r].get('href')
                    try: 
                        md5ss=md5Encode(titles[r])
                        cur.execute("INSERT INTO bid_not(`titles`,`years`,`pinmu_id`,`times`,`pur_ps`,`agencys`,`not_types`,`areas`,`clase`,`url`,`md5`) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", (titles[r],year,k,times_nd,pur_ps_nd,agencys[r],not_types[r],areas_nd,clase_nd,get_url,md5ss))
                    except Exception as e:
                        if '1366' in str(e):
                            titles[r] = titles[r].split('公告')[0] + '公告'
                            md5ss=md5Encode(titles[r])
                            cur.execute("INSERT INTO bid_not(`titles`,`years`,`pinmu_id`,`times`,`pur_ps`,`agencys`,`not_types`,`areas`,`clase`,`url`,`md5`) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", (titles[r],year,k,times_nd,pur_ps_nd,agencys[r],not_types[r],areas_nd,clase_nd,get_url,md5ss))
                    dbconn.commit()
                    try:
                        aaaa=requests.get(get_url,timeout=30)
                        aaaa.encoding = 'utf-8'
                        aaaa=aaaa.text
                    except:
                        aaaa = page404
                    local_main='D:/zhongguoxxx/'+time_data[i]+'/'+str(j)+'/'+str(k)+'/'+times_nd+'/'
                    
                    if not (os.path.exists(local_main)):
                        os.makedirs(local_main)
                    with open(local_main+md5ss+'.html','w',encoding='utf-8') as f:
                         f.write(aaaa)
                    f.close()
                # 增加页数
                a += 1
                print(a)
            except:
                z += 1
                print('============第{}次报错,等待中=============='.format(z))
                # 5次请求失败则终止程序
                if z >= 2:
                    print('================程序异常,退出===============')
                    os.system('python error_email.py')
                    a += 1
                    continue
                # 一次请求失败则暂停2min,等待IP恢复
                time.sleep(120)
                continue
        print('这是类型{} 品目{} ,一共有{}页'.format(j, k, a))
        os.system('python send_email.py')
        cur.close()
            

注:在这个代码中我加了一些其他辅助代码,例如加了一个自动发邮件功能,想要什么功能可以自己加。

转载请注明:南梦婷的博客 » 点击阅读原文


Similar Posts

Comments