【Python爬虫】使用美味汤BeautifulSoup爬取数据并保存为Excel文件

时间:2024-03-19 12:51:04

01 需求描述


我们想要爬取https://yyk.99.com.cn/wuhan/页面上江岸、江汉、硚口、汉阳、武昌、洪山、青山等7个城区的医院信息。医院信息包括医院名称、医院等级、医院性质、占地面积、建筑面积等5个属性。

点击进入网站主页,这里是各个城区的医院名称列表,点击可以进入医院详情页,我们要的数据都在详情页中。
【Python爬虫】使用美味汤BeautifulSoup爬取数据并保存为Excel文件【Python爬虫】使用美味汤BeautifulSoup爬取数据并保存为Excel文件
根据上述情况,我们可以将爬取分为三个过程:

  1. 请求并解析首页,获取所有医院详情页的url,可以将其保存为列表结构供后续调用;
  2. 请求医院详情页url,解析页面内容,得到医院的各个属性信息;
  3. 将医院信息写入xls文件中。

02 获取医院详情页url


无论是何种爬虫,在实际爬取内容之前,都需要先分析网站页面层级结构,想办法汇总需要爬取内容的页面对应的url,后续过程统一请求这些url来获取数据。

我们分析网站的首页,医院详情页的url被保存在a标签的href属性中。点击进入武汉翌高少年中医门诊部的详情页,发下其实际urlhttps://yyk.99.com.cn/huaqiaojiedao/134093/,可知医院的url组织方式为baseUrl + individualUrl,需要将首页a标签上的url附加到基础urlhttps://yyk.99.com.cn之后。
【Python爬虫】使用美味汤BeautifulSoup爬取数据并保存为Excel文件通过分析网页结构,我们可以得知,所有的a标签都位于table标签下的td标签中,我们可以先从页面中找到所有table元素,然后遍历每个table元素的td标签,从td标签下的a标签中即可获取医院href属性。实例代码如下:

def getUrls(self, urlStart):
	print("开始获取所有url...")
	zone_index = [0,1,2,3,4,5,12]                     # 我们指定的7个城区的下标
	hospitalUrl = []                                  # 存放所有医院详情页url
	htmlContent = self.getResponseContent(urlStart)   # 请求首页
	soup = BeautifulSoup(htmlContent, 'lxml')
	tables = soup.find_all('table')                   # 找到所有table标签
	
	# 遍历各个城区
	for zi in zone_index:
	    table_zone = tables[zi]
	    tds = table_zone.find_all('td')               # 找到所有td标签
	    
	    # 遍历每个医院
	    for td in tds:
	        item_url = self.urlBase + td.a.get('href')  # 获得a标签的href属性并组装目标url
	        hospitalUrl.append(item_url) 
	
	return hospitalUrl

03 获取医院属性信息


分析医院详情页,我们可以得到几个属性信息的在页面中的位置:

医院名称:在classwrap-namediv下的h1标签中
医院等级:在classgradespan
医院性质:在classwrap-infodiv下的dl标签下的dd标签下的第二个p标签中
占地面积、建筑面积:在classhospital-infodiv下的p标签中
【Python爬虫】使用美味汤BeautifulSoup爬取数据并保存为Excel文件
【Python爬虫】使用美味汤BeautifulSoup爬取数据并保存为Excel文件
【Python爬虫】使用美味汤BeautifulSoup爬取数据并保存为Excel文件从页面中我们还可以得知,医院名称、等级、性质三个属性可以直接读取标签文本内容的方式来获得,但是医院的占地面积和建筑面积则不一样:一是因为占地面积和建筑面积只是该段落内容的一句话,需要对段落内容进行提取;二是医院介绍信息不一定存在占地面积、建筑面积;三是不一定存在医院介绍。因此,医院占地面积、建筑面积需要采用正则匹配的方式来提取。具体代码如下:

def spider(self, urls):
	print('开始爬取数据...')
	for url in urls:  # 遍历所有医院的url
	    htmlContent = self.getResponseContent(url)
	    soup = BeautifulSoup(htmlContent, 'lxml')
	    item = HospitalItem()
	    
	    div = soup.find('div', attrs={'class': 'wrap-name'})  # 获取医院名称
	    item.HospitalName = soup.find('h1').get_text()
	    
	    item.HospitalLevel = soup.find('span', attrs={'class': 'grade'}).get_text()  # 获取医院等级
	    
	    hospitalProperty = soup.find('div', attrs={'class': 'wrap-info'}).dl.dd.find_all('p')[1].get_text()    # 获取医院性质
	    item.HospitalProperty = hospitalProperty.replace('性质:','')
	    
	    hospital_info = soup.find('div', attrs={'class': 'hospital-info'}).p.get_text()  # 获取医院占地面积
	    pattern = re.compile('[,。]?占地.*?[,。]')
	    match = pattern.search(hospital_info)
	    if match:
	        item.HospitalArea = match[0]
	    else:
	        item.HospitalArea = '无'
	    pattern = re.compile('[,。]?建筑面积.*?[,。]')  # 获取医院建筑面积
	    match = pattern.search(hospital_info)
	    if match:
	        item.HospitalBuild = match[0]
	    else:
	        item.HospitalBuild = '无'
	    
	    self.hospitalItems.append(item)

04 写入Excel文件


Python环境下的Excel文件的读写可以借助xlrt、xlwt两个包,本文需要将医院信息写入Excel文件,因此主要用到xlwt包,实例代码如下:

# 保存数据
def pipelines(self, hospitalItems):
    print('开始保存数据...')
    now = time.strftime('%Y-%m-%d', time.localtime())
    fileName = 'hospital-' + now + '.xls'
    book = xlwt.Workbook(encoding='utf-8', style_compression=0)  # 新建一个工作簿
    sheet = book.add_sheet('hospital')                           # 新建一个工作表
    i = 0
    while i < len(hospitalItems):                                # 遍历所有的医院信息,写入
        item = hospitalItems[i]
        sheet.write(i,0,item.HospitalName)
        sheet.write(i,1,item.HospitalLevel)
        sheet.write(i,2,item.HospitalProperty)
        sheet.write(i,3,item.HospitalArea)
        sheet.write(i,4,item.HospitalBuild)
        i = i + 1
    book.save(fileName)

05 完整代码


用美味汤库来写爬虫还是有一定的套路的:

  1. 定义数据项类,其属性为需要爬取的内容,对应代码中的class HospitalItem(object)部分
  2. 获取页面url,这些url是实际爬取内容的页面的url,对应代码中的def getUrls(self, urlStart)部分
  3. 定义爬虫方法Spider,爬取页面内容,对应代码中的def spider(self, urls)部分
  4. 页面内容后处理以及保存数据,对应代码中的def pipelines(self, hospitalItems)部分
from bs4 import BeautifulSoup
import urllib.request
import re
import codecs
import time
import xlwt


class HospitalItem(object):
    HospitalName = None  # 医院名称
    HospitalLevel = None  # 医院等级
    HospitalProperty = None  # 医院属性
    HospitalArea = None  # 占地面积
    HospitalBuild = None  # 建筑面积


class GetHospital(object):
    def __init__(self):
        self.urlStart = 'https://yyk.99.com.cn/wuhan/'
        self.urlBase = 'https://yyk.99.com.cn'
        self.hospitalUrls = self.getUrls(self.urlStart)  # 拿到所有urls
        self.hospitalItems = []
        self.spider(self.hospitalUrls)  # 爬虫开始爬取
        self.pipelines(self.hospitalItems)  # 爬取的数据经过几个处理流程


    def getUrls(self, urlStart):
        print("开始获取所有url...")
        zone_index = [0,1,2,3,4,5,12]
        hospitalUrl = []
        htmlContent = self.getResponseContent(urlStart)
        soup = BeautifulSoup(htmlContent, 'lxml')
        tables = soup.find_all('table')
        
        # 遍历各个城区
        for zi in zone_index:
            table_zone = tables[zi]
            tds = table_zone.find_all('td')
            
            # 遍历每个医院
            for td in tds:
                item_url = self.urlBase + td.a.get('href')
                hospitalUrl.append(item_url)
        
        return hospitalUrl


    def getResponseContent(self,url):
        try:
            response = urllib.request.urlopen(url)
        except:
            print('~~~~~~')
        else:
            return response.read()


    def spider(self, urls):
        print('开始爬取数据...')
        for url in urls:
            htmlContent = self.getResponseContent(url)
            soup = BeautifulSoup(htmlContent, 'lxml')
            item = HospitalItem()
            div = soup.find('div', attrs={'class': 'wrap-name'})
            item.HospitalName = soup.find('h1').get_text()
            item.HospitalLevel = soup.find('span', attrs={'class': 'grade'}).get_text()
            hospitalProperty = soup.find('div', attrs={'class': 'wrap-info'}).dl.dd.find_all('p')[1].get_text()
            item.HospitalProperty = hospitalProperty.replace('性质:','')
            hospital_info = soup.find('div', attrs={'class': 'hospital-info'}).p.get_text()
            pattern = re.compile('[,。]?占地.*?[,。]')
            match = pattern.search(hospital_info)
            if match:
                item.HospitalArea = match[0]
            else:
                item.HospitalArea = '无'
            pattern = re.compile('[,。]?建筑面积.*?[,。]')
            match = pattern.search(hospital_info)
            if match:
                item.HospitalBuild = match[0]
            else:
                item.HospitalBuild = '无'
            self.hospitalItems.append(item)


    def pipelines(self, hospitalItems):
        print('开始保存数据...')
        now = time.strftime('%Y-%m-%d', time.localtime())
        fileName = 'hospital-' + now + '.xls'
        book = xlwt.Workbook(encoding='utf-8', style_compression=0)
        sheet = book.add_sheet('hospital')
        i = 0
        while i < len(hospitalItems):
            item = hospitalItems[i]
            sheet.write(i,0,item.HospitalName)
            sheet.write(i,1,item.HospitalLevel)
            sheet.write(i,2,item.HospitalProperty)
            sheet.write(i,3,item.HospitalArea)
            sheet.write(i,4,item.HospitalBuild)
            i = i + 1
        book.save(fileName)


if __name__ == '__main__':
    GH = GetHospital()