01 需求描述
我们想要爬取https://yyk.99.com.cn/wuhan/
页面上江岸、江汉、硚口、汉阳、武昌、洪山、青山等7个城区的医院信息。医院信息包括医院名称、医院等级、医院性质、占地面积、建筑面积等5个属性。
点击进入网站主页,这里是各个城区的医院名称列表,点击可以进入医院详情页,我们要的数据都在详情页中。
根据上述情况,我们可以将爬取分为三个过程:
- 请求并解析首页,获取所有医院详情页的url,可以将其保存为列表结构供后续调用;
- 请求医院详情页url,解析页面内容,得到医院的各个属性信息;
- 将医院信息写入
xls
文件中。
02 获取医院详情页url
无论是何种爬虫,在实际爬取内容之前,都需要先分析网站页面层级结构,想办法汇总需要爬取内容的页面对应的url,后续过程统一请求这些url来获取数据。
我们分析网站的首页,医院详情页的url被保存在a
标签的href
属性中。点击进入武汉翌高少年中医门诊部的详情页,发下其实际url
为https://yyk.99.com.cn/huaqiaojiedao/134093/
,可知医院的url
组织方式为baseUrl + individualUrl
,需要将首页a
标签上的url
附加到基础url
即https://yyk.99.com.cn
之后。
通过分析网页结构,我们可以得知,所有的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 获取医院属性信息
分析医院详情页,我们可以得到几个属性信息的在页面中的位置:
医院名称:在class
为wrap-name
的div
下的h1
标签中
医院等级:在class
为grade
的span
中
医院性质:在class
为wrap-info
的div
下的dl
标签下的dd
标签下的第二个p
标签中
占地面积、建筑面积:在class
为hospital-info
的div
下的p
标签中
从页面中我们还可以得知,医院名称、等级、性质三个属性可以直接读取标签文本内容的方式来获得,但是医院的占地面积和建筑面积则不一样:一是因为占地面积和建筑面积只是该段落内容的一句话,需要对段落内容进行提取;二是医院介绍信息不一定存在占地面积、建筑面积;三是不一定存在医院介绍。因此,医院占地面积、建筑面积需要采用正则匹配的方式来提取。具体代码如下:
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 完整代码
用美味汤库来写爬虫还是有一定的套路的:
- 定义数据项类,其属性为需要爬取的内容,对应代码中的
class HospitalItem(object)
部分 - 获取页面url,这些url是实际爬取内容的页面的url,对应代码中的
def getUrls(self, urlStart)
部分 - 定义爬虫方法Spider,爬取页面内容,对应代码中的
def spider(self, urls)
部分 - 页面内容后处理以及保存数据,对应代码中的
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()