使用python爬取行政区划

主要使用到 BeautifulSoup 对网页解析

单线程版本

import requests
from bs4 import BeautifulSoup as bs
import time
import os
import pandas as pd
import re
def retry(url):
    try:
        req=requests.get(url,headers=agent)
    except:
        print('稍等几秒')
        time.sleep(5)
    req.encoding='GBK'#不要用req.encoding=req.apparent_encoding,这会导致编码为gb2312,有很多复杂的地名会无法解码,具体原因请百度
    return req
start_time=time.time()#计时开始
match_tag=['provincetr','citytr','countytr','towntr','villagetr']#这是每一级数据在网站中对应的标签名
choose_ls=[depth*2 if depth<=3 else 3*(depth-1) for depth in range(1,6)]#根据不同级数大小取12位代码前**位
rootdir='分级区划(新)'#设置根目录(凭需求改)
agent={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'}
initurl='http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2020/index.html'#原始网站
filelen=len(os.listdir(rootdir))#检验此时根目录下的文件数量,从而可以从上次爬取的某一级接续爬取
name_ls=['省级','地级','县级','乡级','村级']#表示每一级对应的名称
def execute(href_ls):#传入这一级的所有链接(一个链接代表一个级),对每一链接爬取其下一级的所有名称、代码以及(可能有的)下一级链接
    frame_ls=[]#生成一个空的列表来装载每个链接下面的dataframe格式的名称、代码、链接
    for href_code in range(len(href_ls)):
        href=href_ls[href_code]#取链接
        while True:
            if href:#如果href不为空字符串(下面有解释href为空字符串是怎么来的)
                strip_href=href.replace(href.split('/')[-1],'')#基本网页应该做一个变换以匹配页面中下一级链接href属性的取值(具体请自己看一下)
                req=retry(href)#进行retry以访问到网页
                soup=bs(req.text,'lxml')#beautifulsoup解析
                if depth!=4:
                    col_name=['%s代码'%name_ls[depth],'%s名称'%name_ls[depth],'下一链接']#如果没爬到村级,那么下一级的信息为代码、名称以及其链接
                else:
                    col_name=['村级代码','城乡分类代码','村级名称']#爬到村级了就不需要下一链接了,直接搞信息
                to_tag=soup.find_all(class_=match_tag[depth])#找到对应的标签
                if not to_tag:#没找到有两种原因:一是被反爬了,返回的页面信息是乱码;二是跳过了一级,例如东莞、中山没有县级(区),直接到乡级(镇),需要我们识别
                    for eachdep in range(depth,5):
                        find_tag=soup.find_all(class_=match_tag[eachdep])
                        if find_tag:
                            break#遍历后面的级数,找到了就break
                    if not find_tag:
                        print('出现识别错误')#如果没有找到任何的标签匹配,那么肯定出现了反爬,让程序休眠一段时间再次在while true后面运行(大批量出现问题时,断网重连是个较好的措施)
                        time.sleep(6)
                        continue
                if depth==0:#如果深度为0,即在根网址爬取省级信息
                    #我们不用to_tag识别,转而用属性中href加html的识别所在链接的标签,是因为根网址的源代码结构不大一样,这能更容易的找到每个省份所在的标签
                    to_tag=soup.find_all(attrs={'href':re.compile(r'\d+\.html')})
                    namelist=[each.text for each in to_tag]#每个标签的text即每个省份的名字
                    next_href=[strip_href+each.attrs['href'] for each in to_tag]#用之前的strip_href加到这里标签中的href凑成一个完整的链接
                    name_code=[each.attrs['href'][:2] for each in to_tag]#由于省份代码(包括下面的各级代码)是12位不满的部分取0的,对于省份只需要前两位即可,下同
                    zip_ls=list(zip(name_code,namelist,next_href))#用zip打包这些信息
                elif to_tag:
                    if depth==4:#如果深度为4,开始爬村级,那么只要识别到每个to_tag的信息即可,用列表生成式顺序取
                        zip_ls=[[i.text for i in each.find_all('td')] for each in to_tag]
                    else:
                        zip_ls=[]
                        for each_tag in to_tag:
                            #这里面每一个tag里面都有两个a标签(前面一个a是代码后面一个是名称),而属性的href都是一样的,只要取一个即可
                            level_code=each_tag.find_all('td')[0].text[:choose_ls[depth]]#也是只取代码前一部分即可
                            level_name=each_tag.find_all('td')[1].text
                            #下一级没有链接的情况:村级或者市辖区(举例:广东省-深圳市-市辖区),所以注意区分,如果没找到的话,level_href为空字符串
                            level_href=strip_href+each_tag.a.attrs['href'] if 'href' in str(each_tag) else ''
                            zip_ls.append((level_code,level_name,level_href))#加入zip_ls中
                else:
                    zip_ls=[('','',href)]#如果没有识别到to_tag,但有下一级的信息,证明是东莞等城市,这一级的代码、名称即为空,链接也转为下一级的链接再来识别
            
            else:
                zip_ls=[['']*3]#如果href为空字符串,那么下一级的三项数据全部填为空字符串(即某一路径五级填不满的情况,虽然事实证明应该只有市辖区是如此,但应该防患于未然)
            each_frame=pd.DataFrame(data=zip_ls,columns=col_name)#用dataframe包装这一链接下所有下一级的信息
            each_frame['index']=len(each_frame)*[href_code]#标注一下这一级每个链接所在的索引并传给这个dataframe
            print(href_code)#打印索引以便定位
            frame_ls.append(each_frame)
            break#所有序列完成后break出while true的循环
    return frame_ls#返回这一级链接的所有下一级信息
for depth in range(filelen,5):
    print('开始爬取%s'%name_ls[depth])
    if depth==0:
        href_ls=[initurl]#第0级(最开始)的根节点就只有一个根链接
    else:
        file=pd.read_csv(rootdir+'\\%s.csv'%name_ls[depth-1],keep_default_na=False)
        href_ls=list(file['当前链接'])#其他的通过读取反映
    frame_ls=execute(href_ls)#通过上面的函数返回下一级的信息
    total_frame=pd.concat(frame_ls,ignore_index=True)#将他们组装起来
    if depth==0:
        merge_frame=total_frame#如果是第0级,不用匹配
    else:
        file['index']=file.index#标记这一级的索引
        merge_frame=pd.merge(file,total_frame,on=['index'])#通过下一级信息dataframe中的index和这一级的匹配,merge在一起
        del merge_frame['当前链接']#删除当前链接
    del merge_frame['index']#删除索引列,这个不必要保留,只做merge
    if depth!=4:
        merge_frame.rename(columns={'下一链接':'当前链接'},inplace=True)#将下一链接改名为当前链接,在下一步作为当前链接进行层次遍历
    merge_frame.to_csv(rootdir+'\\%s.csv'%name_ls[depth],index=False)#每一级信息保存
    print('爬到%s,总用时%d秒'%(name_ls[depth],time.time()-start_time))#记录时间

多线程版本

import requests
from bs4 import BeautifulSoup as bs
import time
import os
import pandas as pd
from queue import Queue
from threading import Thread
import re
start_time=time.time()
match_tag=['provincetr','citytr','countytr','towntr','villagetr']
choose_ls=[depth*2 if depth<=3 else 3*(depth-1) for depth in range(1,6)]#根据不同级数大小取12位代码前**位
rootdir='分级区划(多线程)'
agent={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'}
initurl='http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2020/index.html'
filelen=len(os.listdir(rootdir))
name_ls=['省级','地级','县级','乡级','村级']
def execute(in_q,out_q):
    while not in_q.empty():
        href_code=in_q.get()
        href=href_ls[href_code]
        while True:
            if href:
                strip_href=href.replace(href.split('/')[-1],'')
                try:
                    href_req=requests.get(href,headers=agent)
                except:
                    print('稍等几秒')
                    time.sleep(5)
                    continue
                href_req.encoding='GBK'
                soup=bs(href_req.text,'lxml')
                if depth!=4:
                    col_name=['%s代码'%name_ls[depth],'%s名称'%name_ls[depth],'下一链接']
                else:
                    col_name=['村级代码','城乡分类代码','村级名称']
                to_tag=soup.find_all(class_=match_tag[depth])
                if not to_tag:
                    for eachdep in range(depth,5):
                        find_tag=soup.find_all(class_=match_tag[eachdep])
                        if find_tag:
                            break
                    if not find_tag:
                        print('出现识别错误')
                        time.sleep(6)
                        continue
                if depth==0:
                    to_tag=soup.find_all(attrs={'href':re.compile(r'\d+\.html')})
                    namelist=[each.text for each in to_tag]
                    next_href=[strip_href+each.attrs['href'] for each in to_tag]
                    name_code=[each.attrs['href'][:2] for each in to_tag]
                    zip_ls=list(zip(name_code,namelist,next_href))
                else:
                    if to_tag:
                        if depth==4:
                            zip_ls=[[i.text for i in each.find_all('td')] for each in to_tag]
                        else: 
                            zip_ls=[]
                            for each_tag in to_tag:
                                level_code=each_tag.find_all('td')[0].text[:choose_ls[depth]]
                                level_name=each_tag.find_all('td')[1].text
                                level_href=strip_href+each_tag.a.attrs['href'] if 'href' in str(each_tag) else ''
                                zip_ls.append((level_code,level_name,level_href))
                    else:
                        zip_ls=[('','',href)]
            else:
                zip_ls=[['']*3]
            each_frame=pd.DataFrame(data=zip_ls,columns=col_name)
            each_frame['index']=len(each_frame)*[href_code]
            break
        out_q.put(each_frame)
        in_q.task_done()
        print(href_code)
for depth in range(filelen,5):
    print('开始爬取%s'%name_ls[depth])
    if depth==0:
        href_ls=[initurl]
    else:
        file=pd.read_csv(rootdir+'\\%s.csv'%name_ls[depth-1],keep_default_na=False)
        href_ls=list(file['当前链接'])
    href_q=Queue()#定义一个保存当前链接的队列
    out_queue=Queue()#定义一个返回下一级信息的队列
    for index in range(len(href_ls)):
        href_q.put(index)#放进去
    for number in range(20):#开20个线程
        thread=Thread(target=execute,args=(href_q,out_queue,))
        thread.daemon=True
        thread.start()
    href_q.join()
    frame_ls=[]
    for each in range(len(href_ls)):
        frame_ls.append(out_queue.get())#逐个取出返回信息的元素
    total_frame=pd.concat(frame_ls,ignore_index=True)
    if depth==0:
        merge_frame=total_frame
    else:
        file['index']=file.index
        merge_frame=pd.merge(file,total_frame,on=['index'])
        del merge_frame['当前链接']
    del merge_frame['index']
    if depth!=4:
        merge_frame.rename(columns={'下一链接':'当前链接'},inplace=True)
    merge_frame.to_csv(rootdir+'\\%s.csv'%name_ls[depth],index=False)
    print('爬到%s,总用时%d秒'%(name_ls[depth],time.time()-start_time))

原文地址

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值