爬取国家统计局数据正式篇

请看博主的最新博客https://blog.csdn.net/python_solver/article/details/115324659,本篇方法由于较为复杂且效率低下,故应该被淘汰了


最近有些评论问为什么原始的代码爬不了国家统计局了,因为最近国家统计局原数据查询网址http://data.stats.gov.cn在升级改造中,而https://data.stats.gov.cn是没有正规证书的,因此我们需要将原始的代码进行适量修改如下:

import requests
import re
import time
import selenium.webdriver as web
import xlwt
import os
import copy

os.mkdir('D:\\国家统计局分省年度数据')
urla='https://data.stats.gov.cn'#这里就不从官网进了,因为识别到的也是http形式的,只有https能进去
req2=requests.get(urla,verify=False)#设置证书认证为关闭状态
req2.encoding=req2.apparent_encoding
text2=req2.text
match2=re.compile(r'href=(.*?)? >分省年度数据')#找到分省年度数据的网页url
answer=re.findall(match2,text2)[0]
urlb=urla+str(eval(answer))
option=web.ChromeOptions()
option.add_argument('--ignore-certificate-errors')#selenium设置忽略证书的方法
driver=web.Chrome(options=option)
driver.get(urlb)

感谢这位朋友的博客:https://www.cnblogs.com/qican/archive/2020/09/15/13666733.html
后续代码与此前相同
(如果有想爬国家统计局其他数据的朋友,只要将match2那里的分省年度数据修改为数据查询网站题头想要爬取的数据,然后沿着这篇文章的思路结合实际数据对应的网站结构分析即可)

以下为原始内容:

首先对之前那篇博客四百多个访问量说声抱歉,由于4月底后专注于本专业的学习就没有管过python的事,今天刚刚考完,就把之前那篇博客的东西系统梳理一下
我们的目标是尽量爬取近20年所有的分省年度数据,并且按照国家统计局的父/子文件夹方式,把每一项数据写入excel文件中按指定的路径顺序排好。
这里代码不全部发出,会逐步讲解
首先导入requests(请求网站相应并打印源码),正则表达式re库,时间time库(主要用time.sleep来给予网站相应时间,避免代码运行过快导致浏览器跟不上),自动化浏览器爬取框架selenium库,excel写入xlwt库,操作系统控制os库以及copy库(用于深拷贝,下面会讲)

import requests
import re
import time
import selenium.webdriver as web
import xlwt
import os
import copy

下面用os.mkdir方法创建月度数据文件夹,设初始的url链接为国家统计局官网http://www.stats.gov.cn,然后怎么找到地区年度数据的网址呢?
首先,用request库get网址,获得相应req1,为避免网站编码问题,把apparent_encoding赋值给encoding(可省略),用req1.text方法获取网页源码,继而用正则表达式找到标有数据查询网址的地方在这里插入图片描述
(鼠标右键可以看国家统计局官网源代码,我们需要的是href=“”的双引号内的网址),此时用正则表达式r’href="(.*?)"? target="_blank">数据查询’取括号内的网址,?是最小匹配,以免其匹配到下面的文字去。
与上文类似的方法,进入数据查询系统http://data.stats.gov.cn后,用正则表达式去匹配里面的地区年度数据项
在这里插入图片描述
由于href中的网址不是全url链接,所以必须加上之前的抬头data.stats.gov.cn
这样这一段总代码为:

os.mkdir('D:\\国家统计局分省年度数据')
initurl='http://www.stats.gov.cn'
req1=requests.get(initurl)
req1.encoding=req1.apparent_encoding
text1=req1.text
match1=re.compile(r'href="(.*?)"? target="_blank">数据查询')
urla=re.findall(match1,text1)[0]
req2=requests.get(urla)
req2.encoding=req2.apparent_encoding
text2=req2.text
match2=re.compile(r'href=(.*?)? >分省年度数据')
answer=re.findall(match2,text2)[0]
urlb=urla+eval(answer)#因为answer中“”里还有一个'',故用eval把它去掉

最后urlb将输出分省年度数据所在的网址
上面这段过程主要是模仿实际上网浏览时的过程,由官网导入到数据查询的流程,优点是爬取其他数据时直接在分省年度数据那里修改你想要爬取的数据库名称就行了。当然,你也可以省去上面的操作,直接到下面的selenium中输入数据库网址就行了

driver=web.Chrome()
driver.get(urlb)

这里就是启用webdriver的爬取框架,然后自动化模拟Chrome打开浏览器进行相关工作
p.s.再次提醒,如果还不知道chrome的webdriver怎么下,网址在这:https://npm.taobao.org/mirrors/chromedriver/
在这里插入图片描述
注意点开链接后看它的txt文件,会注明适合什么版本号,按照你的Chrome版本安装在你电脑中python文件夹下的Scripts子文件夹里面
接下来,依靠chrome的webdriver,我们得到了月度数据的网站页面,同时最为重要的,依靠driver.page_source方法,我们得到了网页的依托于chrome的解析码,这样可以清晰显示出网站内容的各个元素的内容,也就是我们可以按F12的elements中可以看到的将JavaScript解析后的html格式在这里插入图片描述
有些人可能会问,诶,我看用network项的动态点击可以看到各个数据的request url啊,这样不是不用selenium只用requests也能爬取数据吗(如下图)
在这里插入图片描述
在这里插入图片描述
但是如上图所示,我们不了解request url的结构,在request url中,valuecode究竟有什么规律,它是属于什么条目的,这些只有我们点击每一个条目按F12才知道。爬完后我计算了下咱们所爬取的近20年的数据表总共有3091个,每个表都有一个我们不清楚的valuecode,我们按照这个方法要每一个条目都去寻找,小声说,那写什么爬虫代码呢,还不如直接复制黏贴。

-----------------------------------更新
但是,博主上面犯下了一个错误,那就是博主只是看到了生成表页面下的network里的xhr页面,只发现了每一个栏目的valuecode很复杂,对应不上实际名称,没有细细观察每一次点文件夹出现节点时network是怎么提示的,而忽略的这块正是可以像selenium模仿获取路径的方法。这里感谢下面@纸醉金迷朋友的提醒,按照他的方法结合本人以下的父子节点论述,可以提供一个更快的requests方案!

所以在python中,如果要想在JS代码渲染占绝大多数页面,数据量较大,而且需要不停向服务器请求响应解码时,selenium将是一个不错的选择。
回到之前的叙述,当我们用web.get获取页面时,浏览器将打开页面如下在这里插入图片描述
按F12,我们看到element项中出现了treeZhiBiao栏目
在这里插入图片描述
比如打开一个treeZhiBiao_29,我们发现下面出现了一个text“体育”,这正是网页左侧菜单栏里的一个根栏目大项,所以现在的基本思路就是把这些treeZhiBiao收集起来,进而为下面webdriver读取每一个treeZhiBiao下面的text(读取中文text方便建立文件夹)做铺垫。

initlist=re.findall(r'treeZhiBiao_([\d]+)',driver.page_source)#driver.page_source即解析后的html代码

这里为什么只读取treeZhiBiao后面的数字呢?因为解析码的每个treeZhiBiao含有重复项(比如一个父节点treeZhiBiao下会有点击项以及文字项,这些项里面都包含有重复的treeZhiBiao),所以我们要筛除那些重复项,保证我们后面创建文件夹名字时不会起冲突。所以后续我们定义一个sort函数把这个重复的数字用集合set去掉,再加上抬头treeZhiBiao就可以获得一个列表的父节点数据

def sort(initlist):
    for each in range(len(initlist)):
        initlist[each]=int(initlist[each])
    initlist=list(set(initlist))#集合去重
    initlist.sort()#排序,从小到大,便于分辨整理
    for each in range(len(initlist)):
        initlist[each]='treeZhiBiao_'+str(initlist[each])#加抬头
    return initlist

在initlist中,我们发现总共有三十个treeZhiBiao,除去左侧菜单栏的“指标”大项外,还有一个“地区”大项,这个大项只不过是对指标中省份进行沿海等地理位置的分类,实际数据没有改变,因此我们选择不爬取当中的数据,把这个“地区”对应的treeZhiBiao_2也就是initlist当中的第二项remove掉。然后以这个initlist为基础不动,用copy.deepcopy的方法把深拷贝的initlist的值赋给copylist,这样,在copylist发生改变时,initlist在任何情形下都不会变动,进而进行有顺序的处理。

initlist=sort(initlist)
initlist.remove(initlist[1])#移去地区项
copylist=copy.deepcopy(initlist)

下面的内容分为两大部分,一部分是excel写入的部分,另一部分是通过webdriver的点击自动在文件夹中创造路径的部分,我们先从最难的后一部分讲起。
注意,下面代码内容庞大,我会将这个大的for循环拆开,注意tab键的隔开程度,最后我会把完整的整块代码附在最后。
首先,我们不需要“指标”这个栏目,那么从initlist里面只需要取从1到len(initlist)的索引创建文件夹。由于treeZhiBiao对应的只是解析码,如果要想get到下方的text,我们需要用driver的find_element_by_xpath的属性,之所以不用什么classname等等是因为怕有可能带来的重复问题,而xpath是路径,也是标志每一元素的特有属性
在这里插入图片描述
(点击copy xpath可复制相对路径,为避免复杂,这里都是采用了相对路径而没有全路径,否则会很复杂)
在上图的“综合”指标里,复制相对路径为‘//[@id=“treeZhiBiao_3_ico”]’这个是点击项,而获取父文件夹名称用上面的大指标项treeZhiBiao_3(相对路径即//[@id=“treeZhiBiao_3”])就可以了。

for each in range(1,len(initlist)):
    rootname=driver.find_element_by_xpath('//*[@id="'+initlist[each]+'"]').text#initlist[each]代表不同的父节点treeZhiBiao,通过text方法读取父节点的中文名称
    rootpath='D:\\国家统计局分省年度数据\\'+rootname
    os.mkdir(rootpath)

这样一个父文件夹目录rootpath就建立在系统中了
下面在每一个父节点下(我们把称为一级节点,而这样的一级节点总共有len(initlist)-1个),我们的思路是点击父节点,这样会出现二级节点,点击完后用time的sleep方法给予系统更新page_source的缓冲时间,这时点击后用正则表达式取出treeZhiBiao项并标记为relist,并用前文的sort函数方法去重,remove掉“地区”指标(也就是relist的第二项),与copylist相比多出来的部分正是这个父节点下对应的所有二级节点。然后,把这些多出来的二级节点treeZhiBiao移到新的列表addlist中。紧接着获取这些treeZhiBiao的名称,并在原有的rootpath下加入其名称创建新的路径。最后,再把relist值赋给copylist,进而进行下面对二级节点的操作。
但是在这之中会产生三个问题:一,若是出现不了子节点,也就是点击父节点就是在右侧出现一张表呢?这时我们就要对比relist和copylist的长度,如果relist和copylis长度不一样(知道为什么要用函数sort了吧)就进行上文的操作,一样就需要用个else执行writeexcel函数(下面会讲)进行表的写入writeexcel(rootpath,initlist[each])(initlist[each]代表父节点的所显示的treeZhiBiao项)二、在爬取的过程中,我曾出现点击不了父节点的情况,即点击项不具备交互性。经查明是由于左侧菜单栏父节点所在的位置被挡住了,没有被显示出来,这是需要控制左侧滚动条以及主页面滚动条全部向下移动至底部,这时点击项的功能恢复。在selenium中我们需要输入一段js代码给driver执行。三、子节点同名导致创建路径同名发生错误,这个是我在爬取建筑栏目时看到的,当时我就想这个统计局同一项目咋单列两条?这时就需要在一个子节点名称加个(2)加以区分,但后面在进行三级节点以后的爬取时,数据和路径都会归到那个未标有(2)的文件夹内,之后在把那个带(2)的文件夹删除即可。
这一段代码紧紧衔接上一个代码,注意tab的空位

    while True:
        try:
            driver.find_element_by_xpath('//*[@id="'+str(initlist[each])+'_ico"]').click()#点击交互项
            break
        except:#如果上面的语句click不了用下面的滚动条尝试
            js='document.querySelector("#main-container > div.main-left.left.split-containe").scrollTop=10000'#滚动左侧菜单栏至最底部
            driver.execute_script(js)
            js2='var action=document.documentElement.scrollTop=10000'#滚动整个页面至最底部
            driver.execute_script(js2)
            time.sleep(1)#等待程序反应
            continue#继续尝试上面的try下面的代码,直到成功break循环
    time.sleep(5)#给予浏览器更新page_source时间
    relist=re.findall(r'treeZhiBiao_([\d]+)',driver.page_source)
    relist=sort(relist)#去重
    relist.remove(relist[1])#整个treeZhiBiao系列的更新
    pathlist=[]
    if len(relist)>len(copylist):#检验长度是否大于,大于则执行下方代码,否则在最后writeexcel函数解决
        addlist=[]
        for i in relist:
            if i not in copylist:
                addlist.append(i)
                path=rootpath+('\\'+driver.find_elements_by_xpath('//*[@id="'+i+'"]')[0].text)#注意这里的find_elements是列表,列表中只有一个元素,故只用个[0]即可
                pathlist.append(path)#将二级节点的路径放入pathlist中,等待后文爬取三级节点并归类时有二级节点的路径参照。这里做了个手脚,如果path当中有同名的,也就是pathlist当中会有同名的,这样两个相同的path下的子文件会被归到一个path下(这里利用了list的全能性,即相同的东西完全不干扰)
                try:
                    os.mkdir(path)
                except FileExistsError:#出现同名错误则执行以下代码
                    path+='(2)'
                    os.mkdir(path)#这里的path+(2)不在pathlist里,自然只是一个躯壳,之后对所有的带(2)的文件夹删除即可
            else:
                pass
        copylist=relist#更新copylist

至此,父节点到二级节点的操作完成,下面是二级节点以后节点的操作步骤。(这里仅讲述二级节点到三级节点的基本原理,三级节点到以后的可照推)
从上面的addlist我们得到了所有二级节点的treeZhiBiao,那么我们用for循环依次遍历所有的二级节点进行点击,设其中一个二级节点的路径为the_path,点击这个二级节点,如果出来三级节点,那我们用上文的copylist和relist组合对比获取它们的名称,将多出来的三级节点treeZhiBiao代码放入空列表addlist2中,并按上文一样的步骤将他们的名称和二级节点对应the_path加在一起组成新的path,然后放入新的空列表pathlist2中,这里又有一个新问题出现,就是可能的计量单位‘/’(即多少/千克(吨)等等)会与路径的隔位符‘//’,‘/’相冲突造成错误,那么这里只要把‘/’替换成’每‘就行了。如果没出现第三节点,设第二节点的treeZhiBiao代码为treecode,执行函数writeexcel(the_path,treecode)即可。后面,我们把addlist2赋值给addlist,把pathlist2赋值给pathlist,此时第三节点就和上文的二级节点一样对下一级节点进行处理,直到addlist2为空或者全部的子节点都用writeexcel方法执行,这时就要用while True+break/continue的模式处理(因为我们不知道节点最多有多少个)这里的代码采用后者:
依然注意下tab的情况,跟上文紧密连接

        while True:
            count=0#对子节点数量进行计数
            addlist2=[]
            pathlist2=[]
            for x in range(len(addlist)):#遍历所有addlist
                treecode=addlist[x]
                the_path=pathlist[x]
                while True:
                    try:
                        driver.find_element_by_xpath('//*[@id="'+treecode+'_ico"]').click()
                        break
                    except:
                        js='document.querySelector("#main-container > div.main-left.left.split-containe").scrollTop=10000'
                        driver.execute_script(js)
                        js2='var action=document.documentElement.scrollTop=10000'
                        driver.execute_script(js2)
                        time.sleep(1)
                        continue
                time.sleep(4)#滚动条操作等和上文相同
                relist=list(set(re.findall(r'treeZhiBiao_([\d]+)',driver.page_source)))
                relist=sort(relist)
                relist.remove(relist[1])
                if len(relist)>len(copylist):
                    for y in relist:
                        if y not in copylist:
                            addlist2.append(y)
                            path=the_path+('\\'+driver.find_elements_by_xpath('//*[@id="'+y+'"]')[0].text)
                            if '/' in path:
                                path=path.replace('/','每')#替换
                            else:
                                pass
                            try:
                                os.mkdir(path)
                            except FileExistsError:
                                path+='(2)'
                                os.mkdir(path)
                            pathlist2.append(path)#将子节点的path纳入pathlist2中
                        else:
                            pass
                    count+=1#有子节点,count加1,没有不加
                    copylist=relist
                else:
                    writeexcel(the_path,treecode)
            if count==0:
                break#已经没有任何多出来的子节点,break掉循环,进入下一父节点(第一节点)
            else:
                pathlist=pathlist2#更新pathlist进行下一子结点操作
                addlist=addlist2#更新addlist进行下一子结点操作
                continue

其他没有注明的部分在上文中已提到,故不再赘述
最后加一个对第一节点数据如果没有子节点时直接爬取右侧表格对应:

    else:
        writeexcel(rootpath,initlist[each])

总体的代码大家可以复制黏贴下来,对照着每一步看

for each in range(1,len(initlist)):
    rootname=driver.find_element_by_xpath('//*[@id="'+initlist[each]+'"]').text
    rootpath='D:\\国家统计局分省年度数据\\'+rootname
    os.mkdir(rootpath)
    while True:
        try:
            driver.find_element_by_xpath('//*[@id="'+str(initlist[each])+'_ico"]').click()
            break
        except:
            js='document.querySelector("#main-container > div.main-left.left.split-containe").scrollTop=10000'
            driver.execute_script(js)
            js2='var action=document.documentElement.scrollTop=10000'
            driver.execute_script(js2)
            time.sleep(1)
            continue
    time.sleep(5)
    relist=list(set(re.findall(r'treeZhiBiao_([\d]+)',driver.page_source)))
    relist=sort(relist)
    relist.remove(relist[1])
    pathlist=[]
    if len(relist)>len(copylist):
        addlist=[]
        for i in relist:
            if i not in copylist:
                addlist.append(i)
                path=rootpath+('\\'+driver.find_elements_by_xpath('//*[@id="'+i+'"]')[0].text)
                pathlist.append(path)
                try:
                    os.mkdir(path)
                except FileExistsError:
                    path+='(2)'
                    os.mkdir(path)
            else:
                pass
        copylist=relist
        while True:
            count=0
            addlist2=[]
            pathlist2=[]
            for x in range(len(addlist)):
                treecode=addlist[x]
                the_path=pathlist[x]
                while True:
                    try:
                        driver.find_element_by_xpath('//*[@id="'+treecode+'_ico"]').click()
                        break
                    except:
                        js='document.querySelector("#main-container > div.main-left.left.split-containe").scrollTop=10000'
                        driver.execute_script(js)
                        js2='var action=document.documentElement.scrollTop=10000'
                        driver.execute_script(js2)
                        time.sleep(1)
                        continue
                time.sleep(4)
                relist=re.findall(r'treeZhiBiao_([\d]+)',driver.page_source)
                relist=sort(relist)
                relist.remove(relist[1])
                if len(relist)>len(copylist):
                    for y in relist:
                        if y not in copylist:
                            addlist2.append(y)
                            path=the_path+('\\'+driver.find_elements_by_xpath('//*[@id="'+y+'"]')[0].text)
                            if '/' in path:
                                path=path.replace('/','每')
                            else:
                                pass
                            try:
                                os.mkdir(path)
                            except FileExistsError:
                                path+='(2)'
                                os.mkdir(path)
                            pathlist2.append(path)
                        else:
                            pass
                    count+=1
                    copylist=relist
                else:
                    writeexcel(the_path,treecode)
            if count==0:
                break
            else:
                pathlist=pathlist2
                addlist=addlist2
                continue
    else:
        writeexcel(rootpath,initlist[each])

现在开始处理writeexcel函数部分
首先,writeexcel(rootpath,treecode)中的rootpath即写入excel保存的根目录,这个rootpath(在电脑中的路径)对应的treecode(treeZhiBiao)在前文当中的左侧菜单栏已经没有任何子节点,即其为右侧数据框内一系列数据与名称组合成的excel表的集合。
首先,我们要爬取的数据是省份和这个rootpath下对应的各个小栏目
在这里插入图片描述
以上图的行政区划举例,有省份下拉框以及地级区划数、地级市数等一系列小栏目。通常来说,我们希望的是把小栏目整理成excel的表名,每个小栏目一个excel表,好在之后的数据分析中可以同样的栏目中对不同的省份进行对比。那么我们需要在省份的地区下拉框内点击序列,把地区选项框转化为指标选项框。而在解析码中,先点击地区下拉框,所在xpath为//[@id=“mySelect_reg”]/div[2]/div[1],再点击序列//[@id=“mySelect_reg”]/div[2]/div[2]/div[2]/ul/li[1],完成click的操作
在这里插入图片描述

def writeexcel(rootpath,treecode):
    while True:
        try:
            driver.find_element_by_xpath('//*[@id="mySelect_reg"]/div[2]/div[1]').click()#点击地区下拉框
            driver.find_element_by_xpath('//*[@id="mySelect_reg"]/div[2]/div[2]/div[2]/ul/li[1]').click()#点击第一个序列项,转化为指标框
            break
        except:
            time.sleep(1)#如果点击没找到相关元素,重新尝试点击
            continue
    time.sleep(2)#等待浏览器反应进行下一步操作

之后是一次循环,每次点击指标框,在指标框中依次选择指标,等待浏览器加载完数据后进行数据表的加载与爬取。比如,我们以下图这个为例在这里插入图片描述
我们点击地级区划数(注:不要点击序列,否则会转换成地区指标项),它的xpath是//*[@id=“mySelect_zb”]/div[2]/div[2]/div[2]/ul/li[2],其实仔细观察一下li后面框框里那个数字,你就会发现它是每一个指标在下拉框里的位置在这里插入图片描述
(看到左右侧那里,与地级区划数同列排布的,还有地级市数、县级区划数等等,地级区划数是li[2],后面两个是li[3]和li[4],而li[1]就是序列项)
由此可以设一个countnum=2,从li[2]开始一直往后点击各个指标,而我们不知道指标项具体有多少,故用while True和try-except的结构,当countnum已经没有所显示的指标时,try里面的代码运行错误而跳到except语句中break掉整个while循环,进而结束函数的运作
(注意下面的tab,紧接着上文的代码)

	countnum=2#初始countnum值,在下文的while循环最后每一次countnum都会加一,直到try语句出错break为止
    while True:
        try:
            while True:
                try:
                    driver.find_element_by_xpath('//*[@id="mySelect_zb"]/div[2]/div[1]').click()#在地区下拉框点击序列转化成指标下拉框时会关闭下拉框,因此需要重新点击
                    time.sleep(2)
                    break
                except:
                    continue
            excel_name=driver.find_element_by_xpath('//*[@id="mySelect_zb"]/div[2]/div[2]/div[2]/ul/li['+str(countnum)+']').text#取每一个指标的xpath对应的text及文字,赋值给excel_name,此即excel文件的名字
            driver.find_element_by_xpath('//*[@id="mySelect_zb"]/div[2]/div[2]/div[2]/ul/li['+str(countnum)+']').click()#点击指标项,更新数据表
            time.sleep(2)

下面针对每一个countnum我们爬取数据表,首先是提取数据表中抬头年份的数据
在这里插入图片描述
可以从右侧看到,2019一直到2000是并列在th项下面的,2019年对应的是//*[@id=“table_main”]/thead/tr/th[2],可以令初始值i=2,然后一次累加得到一个年份的累计列表

            def extract_year():
                yearlist=[]
                i=2
                while True:
                    try:
                        z=driver.find_elements_by_xpath('//*[@id="table_main"]/thead/tr/th['+str(i)+']')
                        if type(z)==list:#有时候z会输出一个只带一个driver.find_elements_by_path类的元素的列表,有时候又直接输出类名,故在这里做好两手准备
                            text=z[0].text
                        else:
                            text=z.text 
                        yearlist.append(text)
                        i+=1
                    except:#国家统计局有一种很奇怪的表是空头数据表,即加载完数据后什么年份数据都没有,故遇到这种表直接break,输出一个空的yearlist等待之后检验
                        break
                return yearlist

这里之所以设置一个爬取年份的函数而不直接另数据列表为2000-2019的格式除了有空头数据外,还有一个原因是博主还要爬取其他七项大的数据库,故可以迁移使用(还有一个原因,如果几年后不是2000-2019了,这时如果再爬取也会和统计局数据一样更新,不必再输入了)。在那些数据库中有时候数据的最新时间已经超过了一定的年限范围(最近20年,最近36个月以及最近18季度),这时不用点击右侧的最近20年/36个月/18季度的框了,直接根据extract_year函数的第一项也就是最前一项判断,如果超出了年限,则整个数据表不用爬取,直接break掉就行了,节省时间。但在这里,20年是个很长的年限,很难有数据表是超出这个年限外的,所以可以认为所有数据表都在这个范畴。因此下面进行检验和提取

            yearlist=extract_year()
            while True:#可能是while True写多了,这一步是不用加while True的(因为后面反正所有情况都要break掉循环结束指标爬取),但考虑到tab的地方太多,调整较为复杂,因此你们可以改进一下~~(后面相应的break改成pass就ok)
                if yearlist==[]:#检验yearlist是否为空,为空则这一指标项则不爬取,也即跳出了搜集数据成表的循环
                    break
                else:
                    try:
                        checkyear=int(yearlist[0].split('年')[0])#检验在未点击最近20年数据项之前最近的年份是否低于2000年,低于2000年则break循环,else进行数据爬取
                        if checkyear<2000:
                            break
                        else:
                            while True:
                                try:
                                    driver.find_elements_by_xpath('//*[@id="mySelect_sj"]/div[2]/div[1]')[0].click()#点击右侧的时间菜单栏,出现下拉项
                                    driver.find_elements_by_xpath('//*[@id="mySelect_sj"]/div[2]/div[2]/div[2]/ul/li[3]')[0].click()#点击时间下拉项中的最后一项,也就是最近20年
                                    break
                                except:
                                    time.sleep(1)
                                    continue#如果出现可能的点击失败,等待浏览器反应时间,继续执行try语句操作
                            yearlist=extract_year()#点击最近20年后,出现真正的2000-2019年的年份列表
                            for each in range(len(yearlist)):
                                yearlist[each]=yearlist[each].replace('年','')#将年份的年剔除
                            i=1#数据表的起始点,i代表行的位置
                            data_contain=0#数据总量初始值设为0
                            wk=xlwt.Workbook()#建立一个workbook对象
                            sh=wk.add_sheet('sheet1')#建立sheet对象,添加表sheet1
                            sh.write(0,0,'年份')#第一行第一列写抬头年份
                            for each in range(len(yearlist)):
                                sh.write(each+1,0,yearlist[each])#第一列除第一行外,年份按yearlist依次读取写入,越往年份小的地方行数越大(each+1代表行,0代表列,注意这里都是从0开始计的!)
                            while True:
                                try:
                                    name=driver.find_elements_by_xpath('//*[@id="table_main"]/tbody/tr['+str(i)+']/td[1]')[0].text
                                    y=1#y代表列的位置,举例:如果i=1而y=1,则下面的xpath对应的xpath的text是字符串"北京",如果y=2代表的是北京该指标下2019年的数据,y=多少多少往后就是几几年的数据,一一对应
                                    while True:
                                        try:
                                            data=driver.find_elements_by_xpath('//*[@id="table_main"]/tbody/tr['+str(i)+']/td['+str(y)+']')[0].text#找到数据的位置并获取其文字
                                            if data!='':#如果该行该列对应的数据不为空,则数据总量加一,否则不加
                                                data_contain+=1
                                            else:
                                                pass
                                            try:
                                                sh.write(y-1,i,float(data))#注意一下这里统计局的数据的行列和excel表中的行列是转置过来的,因此变换一下y和i的位置
                                            except ValueError:#如果出错,代表数据(字符串类型)不能转换为浮点型,这是因为数据是省市的名称,因此直接写入data就行
                                                sh.write(y-1,i,data)
                                            y+=1
                                        except:
                                            break#此时列已经溢出,try语句出现错误,则break掉列的循环,转而增加i行的个数,在新的i行下爬取数据
                                    i+=1
                                except:#此时行i溢出,已经找不到对应的xpath,因此break掉循环,表的写入结束
                                    break
                            if data_contain==0:#如果没有任何数据,则不让它成表(因为没有意义)
                                break
                            else:
                                if '/' in excel_name:#同上文解释
                                    excel_name=excel_name.replace('/','每')
                                else:
                                    pass
                                wk.save(rootpath+'\\'+excel_name+'.xls')#只要data_contain超过0,则将这一指标成表保存
                                break#break掉这一个指标的爬取
                    except:
                        break#有其他不可预知的错误情况也不让它成表,停止指标爬取
            countnum+=1#进行下一指标项的点击爬取
        except:
            break#已经找不到countnum所在的指标项,因此跳出整个函数,即writeexcel执行完毕

data_contain是为了弥补在前面yearlist检验当中可能放过的某些没有任何数据的空头文件,保证所有的文件都是有数据的
writeexcel函数的效果如图:
在这里插入图片描述
(综合的父节点下行政区划的二节点文件夹中各个指标的excel文件)
在这里插入图片描述
(行政区划的excel示例)
所有程序执行完后,用driver.close()关闭程序
这里提醒一下:
1、这里的代码已经尽可能地规避了所有可能出错的风险(你可以从成堆的try-except)当中看出这一点,而且time.sleep也给足了浏览器反应的时间,但仍然不排除因为网络不佳、服务器响应缓慢的原因导致较小概率的出错可能
2、可以看浏览器的执行情况,但是在自动化控制页面一定一定不要随意点击,否则会导致代码与人工的相互干扰导致出错
3、万一遇到这种情况也不要急,看到浏览器爬到哪个父节点了,然后再在最开始的for each in range(1,len(initlist))中把1修改一下,修改成出错的父节点,然后创建另一个根目录国家统计局2,继续后面的爬取,后面爬完后再把两个文件夹合在一起即可
以这个代码为基础,可以继续爬取其他七个数据查询的大数据库,其中国家层次数据是没有“地区”这个大项的,所以最开始的initlist不用移除tree
ZhiBiao,后面创建路径的方法可以直接复制粘贴,但是writeexcel需要改一下,因为国家层面数据表中已经没有地区或指标下拉框,因此那步可以去除,此外年份判断那里根据季度、月度数据需要做实时修改
由于是第一次做这样大面积的爬取,所以代码肯定有不足或值得修改的部分,首先路径创建那里就有一些代码存在重复部分,但因本人水平有限,只能把父节点到二节点和二节点到以后节点的过程割裂,期待有高手能够改进。
最后的提醒(供参考):
国家统计局分省年度数据耗时22小时爬取(总计3076张表),总年度数据耗时18小时(总计1912张表),总月度数据7小时,分省月度数据7小时,主要城市年度价格4个半小时,其他均在90分钟以内
如果想耐住性子,期间电脑最好不要使用其他程序,不要待机睡眠(博主是让自家电脑充电时设置了永不睡眠模式,爬完后再改),如果只是想爬取部分数据,那只用修改一下循环就行。在这里插入图片描述
最初是由于csdn上没有网站系统爬取国家统计局的博客,所以抱着试一试的心态完成了这次爬取(前后几乎断断续续一周的时间),就尽我的绵薄之力帮助一些有需要的人吧
附总代码:

import requests
import re
import time
import selenium.webdriver as web
import xlwt
import os
import copy
os.mkdir('D:\\国家统计局分省年度数据')
initurl='http://www.stats.gov.cn'
req1=requests.get(initurl)
req1.encoding=req1.apparent_encoding
text1=req1.text
match1=re.compile(r'href="(.*?)"? target="_blank">数据查询')
urla=re.findall(match1,text1)[0]
req2=requests.get(urla)
req2.encoding=req2.apparent_encoding
text2=req2.text
match2=re.compile(r'href=(.*?)? >分省年度数据')
answer=re.findall(match2,text2)[0]
urlb=urla+str(eval(answer))
driver=web.Chrome()
driver.get(urlb)
initlist=re.findall(r'treeZhiBiao_([\d]+)',driver.page_source)
def sort(initlist):
    for each in range(len(initlist)):
        initlist[each]=int(initlist[each])
    initlist=list(set(initlist))
    initlist.sort()
    for each in range(len(initlist)):
        initlist[each]='treeZhiBiao_'+str(initlist[each])
    return initlist
def writeexcel(rootpath,treecode):
    while True:
        try:
            driver.find_element_by_xpath('//*[@id="mySelect_reg"]/div[2]/div[1]').click()
            driver.find_element_by_xpath('//*[@id="mySelect_reg"]/div[2]/div[2]/div[2]/ul/li[1]').click()
            break
        except:
            time.sleep(1)
            continue
    time.sleep(2)
    countnum=2
    while True:
        try:
            while True:
                try:
                    driver.find_element_by_xpath('//*[@id="mySelect_zb"]/div[2]/div[1]').click()
                    time.sleep(2)
                    break
                except:
                    continue
            excel_name=driver.find_element_by_xpath('//*[@id="mySelect_zb"]/div[2]/div[2]/div[2]/ul/li['+str(countnum)+']').text
            driver.find_element_by_xpath('//*[@id="mySelect_zb"]/div[2]/div[2]/div[2]/ul/li['+str(countnum)+']').click()
            time.sleep(2)
            def extract_year():
                yearlist=[]
                i=2
                while True:
                    try:
                        z=driver.find_elements_by_xpath('//*[@id="table_main"]/thead/tr/th['+str(i)+']')
                        if type(z)==list:
                            text=z[0].text
                        else:
                            text=z.text 
                        yearlist.append(text)
                        i+=1
                    except:
                        break
                return yearlist
            yearlist=extract_year()
            while True:
                if yearlist==[]:
                    break
                else:
                    try:
                        checkyear=int(yearlist[0].split('年')[0])
                        if checkyear<2000:
                            break
                        else:
                            while True:
                                try:
                                    driver.find_elements_by_xpath('//*[@id="mySelect_sj"]/div[2]/div[1]')[0].click()
                                    driver.find_elements_by_xpath('//*[@id="mySelect_sj"]/div[2]/div[2]/div[2]/ul/li[3]')[0].click()
                                    break
                                except:
                                    time.sleep(1)
                                    continue
                            yearlist=extract_year()
                            for each in range(len(yearlist)):
                                yearlist[each]=yearlist[each].replace('年','')
                            datadict={}
                            i=1
                            data_contain=0
                            wk=xlwt.Workbook()
                            sh=wk.add_sheet('sheet1')
                            sh.write(0,0,'年份')
                            for each in range(len(yearlist)):
                                sh.write(each+1,0,yearlist[each])
                            while True:
                                try:
                                    name=driver.find_elements_by_xpath('//*[@id="table_main"]/tbody/tr['+str(i)+']/td[1]')[0].text
                                    y=1
                                    while True:
                                        try:
                                            data=driver.find_elements_by_xpath('//*[@id="table_main"]/tbody/tr['+str(i)+']/td['+str(y)+']')[0].text
                                            if data!='':
                                                data_contain+=1
                                            else:
                                                pass
                                            try:
                                                sh.write(y-1,i,float(data))
                                            except ValueError:
                                                sh.write(y-1,i,data)
                                            y+=1
                                        except:
                                            break
                                    i+=1
                                except:
                                    break
                            if data_contain==0:
                                break
                            else:
                                if '/' in excel_name:
                                    excel_name=excel_name.replace('/','每')
                                else:
                                    pass
                                wk.save(rootpath+'\\'+excel_name+'.xls')
                                break
                    except:
                        break
            countnum+=1
        except:
            break
initlist=sort(initlist)
initlist.remove(initlist[1])
copylist=copy.deepcopy(initlist)
for each in range(1,len(initlist)):
    rootname=driver.find_element_by_xpath('//*[@id="'+initlist[each]+'"]').text
    rootpath='D:\\国家统计局分省年度数据\\'+rootname
    os.mkdir(rootpath)
    while True:
        try:
            driver.find_element_by_xpath('//*[@id="'+str(initlist[each])+'_ico"]').click()
            break
        except:
            js='document.querySelector("#main-container > div.main-left.left.split-containe").scrollTop=10000'
            driver.execute_script(js)
            js2='var action=document.documentElement.scrollTop=10000'
            driver.execute_script(js2)
            time.sleep(1)
            continue
    time.sleep(5)
    relist=list(set(re.findall(r'treeZhiBiao_([\d]+)',driver.page_source)))
    relist=sort(relist)
    relist.remove(relist[1])
    pathlist=[]
    if len(relist)>len(copylist):
        addlist=[]
        for i in relist:
            if i not in copylist:
                addlist.append(i)
                path=rootpath+('\\'+driver.find_elements_by_xpath('//*[@id="'+i+'"]')[0].text)
                pathlist.append(path)
                try:
                    os.mkdir(path)
                except FileExistsError:
                    path+='(2)'
                    os.mkdir(path)
            else:
                pass
        copylist=relist
        while True:
            count=0
            addlist2=[]
            pathlist2=[]
            for x in range(len(addlist)):
                treecode=addlist[x]
                the_path=pathlist[x]
                while True:
                    try:
                        driver.find_element_by_xpath('//*[@id="'+treecode+'_ico"]').click()
                        break
                    except:
                        js='document.querySelector("#main-container > div.main-left.left.split-containe").scrollTop=10000'
                        driver.execute_script(js)
                        js2='var action=document.documentElement.scrollTop=10000'
                        driver.execute_script(js2)
                        time.sleep(1)
                        continue
                time.sleep(4)
                relist=list(set(re.findall(r'treeZhiBiao_([\d]+)',driver.page_source)))
                relist=sort(relist)
                relist.remove(relist[1])
                if len(relist)>len(copylist):
                    for y in relist:
                        if y not in copylist:
                            addlist2.append(y)
                            path=the_path+('\\'+driver.find_elements_by_xpath('//*[@id="'+y+'"]')[0].text)
                            if '/' in path:
                                path=path.replace('/','每')
                            else:
                                pass
                            try:
                                os.mkdir(path)
                            except FileExistsError:
                                path+='(2)'
                                os.mkdir(path)
                            pathlist2.append(path)
                        else:
                            pass
                    count+=1
                    copylist=relist
                else:
                    writeexcel(the_path,treecode)
            if count==0:
                break
            else:
                pathlist=pathlist2
                addlist=addlist2
                continue
    else:
        writeexcel(rootpath,initlist[each])
driver.close()


  • 26
    点赞
  • 119
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论
回答: 要实现Java国家统计局爬虫,你可以使用jsoup库来进行网页解析和数据提取。首先,你需要在项目的pom.xml文件中引入jsoup依赖,如下所示:\[2\] ```xml <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.9.2</version> </dependency> ``` 然后,你可以创建一个Java类,例如JavaJsoupTest,来编写你的爬虫代码。在这个类中,你可以使用jsoup的API来发送HTTP请求,解析网页内容,并提取你需要的数据。下面是一个简单的示例代码:\[1\] ```java public class JavaJsoupTest { JavaJsoupUtil util = new JavaJsoupUtil(); @Test public void cityTest(){ //省 List<SysCitys> sysAreas = util.getProvinces(); System.out.println(sysAreas.size()); System.err.println("爬虫相应数据为:"+ JSONObject.toJSONString(sysAreas)); /* //市 List<SysCitys> sysAreas = util.getCitys("http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/50.html","500000"); System.err.println("爬虫相应数据为:"+ JSONObject.toJSONString(sysAreas));*/ } } ``` 在这个示例中,JavaJsoupTest类中的cityTest方法使用了JavaJsoupUtil类的getProvinces方法来获取省级数据,并打印出结果。你可以根据需要修改代码来实现你的具体需求。 #### 引用[.reference_title] - *1* *2* [java爬取国家统计局之统计用区划和城乡划分代码(省市区三级)](https://blog.csdn.net/lock_xuanqing/article/details/126873710)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kappuccinno

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值