爬虫实战带上代码讲解(以爬取好大夫为例)

前言:

我感觉之前讲的爬虫,太纸面化了,需要给一些实例来帮助理解。毕竟爬虫这项技能,我们经常可能用到,通常用于爬虫数据来训练模型。

延续上一篇文章所说将爬虫分为四个主要部分:

获取网页源代码

解析网页并提取数据

实现自动化抓取逻辑

保存数据到文件(如 execl)

第一步:获取网页源代码

要获取网页内容,最直接的方式是使用 requests 库。帮助我们发送 HTTP 请求并获取网页内容。以下是一个基本示例:

import requests

url = "https://example.com"  # 目标网页地址
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36", 
    "Cookie": "your_cookie_value"
}

# 发送请求并获取响应
response = requests.get(url, headers=headers)
html_content = response.text  # 获取网页的 HTML 源代码

参数说明:

url:目标网页的地址。

headers:请求头,用于伪装成真实浏览器,避免被识别为爬虫。关键字段包括:

User-Agent:指定用户代理信息。

Cookie:某些网站需要登录后才能访问完整内容,Cookie 可以帮助模拟登录状态。

得按照实践情况来添加参数

小贴士:

用F12,查看页面请求时的Cookies,便于构造请求头。

使用 response.status_code 确认请求是否成功(200 表示成功)。

第二步:解析网页并提取数据

获取网页源代码后,需要从中提取出关键信息。这一步可以借助 BeautifulSoup。

示例:

def parse_html(html):
    soup = BeautifulSoup(html, 'html.parser')

    old_disease_info = soup.find('p', class_='diseaseinfo') #疾病信息
    disease_info=get_all_text(old_disease_info)
    #print(disease_info)

    header_title = soup.find('h1', class_='header-title')
    text = header_title.get_text(strip=True)
    type = text.split('- ')[-1]#问诊类型
    #print(type)

    meta_og_url = soup.find('meta', property='og:url')
    disease_url = meta_og_url.get('content') #病例url
    #print(disease_url)

    a_tag = soup.find('a', attrs={'target': '_blank', 'rel': 'noreferrer'})
    doctor_url = a_tag.get('href')#医生url
    #print(doctor_url)


    card_info_text = soup.find('div', class_='card-info-text')
    doctor_info=get_all_text(card_info_text) #医生简介
    #print(doctor_info)

    speciality_div = soup.select_one('div[ref="specialityBox"]')
    if speciality_div:
        doctor_speciality = get_all_text(speciality_div)  # 医生擅长
    else:
        doctor_speciality='无说明'
    #print(doctor_speciality)

    doctor_card_service = soup.find('div', class_='doctor-card-service clearfix')
    doctor_service = get_all_text(doctor_card_service) #医生服务质量
    #print(doctor_service)

    suggestions_marginLeft0 = soup.find('section', class_='suggestions')
    doctor_suggestions = get_all_text(suggestions_marginLeft0) #医生建议
    #print(doctor_suggestions)

    msgboard_js_msgboard = soup.find('section', class_='msgboard')
    message= get_all_text(msgboard_js_msgboard) #医生与患者交流
    #print(message)

    return disease_info,type,disease_url,doctor_url,doctor_info,doctor_speciality,doctor_service,doctor_suggestions,message

这部分是用于解析解析单个病例详细页

该页面的所需内容如下

根据以下图片来进行解析网页并提取数据

就简单拿示例来进行讲解:

1.soup = BeautifulSoup(html, 'html.parser')

html.parser 作为解析器,将 html 解析成 soup 对象,方便后续提取数据。

2.old_disease_info = soup.find('p', class_='diseaseinfo') # 疾病信息
disease_info = get_all_text(old_disease_info)

查找<p>标签,并且该标签的 class 是 "diseaseinfo",即疾病描述信息。

get_all_text(old_disease_info) 是一个自定义函数,它会递归提取该标签及其子标签的所有文本信息。之所以这里创建get_all_text()函数是因为<p>的信息不是连续的而是分布在里面的子标签里面

其代码为

def get_all_text(element):
    text = ''
    for content in element.contents:
        if content.name is None:  
            text += content
        else:
            text += get_all_text(content)  # 递归获取子元素的文本
    return text

如下

3.header_title = soup.find('h1', class_='header-title')
text = header_title.get_text(strip=True)
type = text.split('- ')[-1] # 问诊类型

查找 <h1> 标签,class 为 "header-title",表示文章标题。

get_text(strip=True) 获取标题文本内容,并去除两端的空白字符。

text.split('- ')[-1] 提取问诊类型(格式为 "描述 - 问诊类型" 这样的格式)。

如下

4.meta_og_url = soup.find('meta', property='og:url')
disease_url = meta_og_url.get('content') # 病例url

查找 标签,property 属性为 "og:url",通常用于存储当前页面的 URL。

get('content') 提取 content 属性值,即病例的 URL。

如下

5.a_tag = soup.find('a', attrs={'target': '_blank', 'rel': 'noreferrer'})
doctor_url = a_tag.get('href') # 医生url

get('href') 获取医生的个人主页链接。

6.card_info_text = soup.find('div', class_='card-info-text')
doctor_info = get_all_text(card_info_text) # 医生简介

查找 div 标签,class 为 "card-info-text",其中包含医生的简要信息。

get_all_text(card_info_text) 提取该 div 的所有文本信息。

也是因为其信息在子标签里面

如下

7.speciality_div = soup.select_one('div[ref="specialityBox"]')
if speciality_div:
doctor_speciality = get_all_text(speciality_div) # 医生擅长
else:
doctor_speciality = '无说明'

有些病例url里面医生是没有填写擅长的,所以如果 speciality_div 存在,则提取文本,否则返回 "无说明"。

如下

8.doctor_card_service = soup.find('div', class_='doctor-card-service clearfix')
doctor_service = get_all_text(doctor_card_service) # 医生服务质量

查找 div 标签,class 为 "doctor-card-service clearfix",存储医生的服务评分或质量评价。

get_all_text(doctor_card_service) 提取该 div 内的文本。

如下

9.suggestions_marginLeft0 = soup.find('section', class_='suggestions')
doctor_suggestions = get_all_text(suggestions_marginLeft0) # 医生建议

查找 section 标签,class 为 "suggestions",表示医生的建议。

get_all_text(suggestions_marginLeft0) 提取医生对患者的建议文本。

(因为我现在忘记密码了,是未登录状态,因此展示不了)

10.msgboard_js_msgboard = soup.find('section', class_='msgboard')
message = get_all_text(msgboard_js_msgboard) # 医生与患者交流

查找 section 标签,class 为 "msgboard",表示医生和患者的留言互动。

get_all_text(msgboard_js_msgboard) 提取交流信息的文本。

(因为我现在忘记密码了,是未登录状态,因此展示不了)

第三步:实现自动化抓取逻辑

为了抓取页面信息,可能需要采用构造分页参数等操作。

如本次爬取好大夫病例时,我们可以看到

在源代码下结构为

我们需要爬取里面的每种类型的病例

因此我们目的是从 HTML 页面中提取大类和小类的信息,并将它们组织到一个字典中。字典的结构如下:

{
    '大类1': [
        {'name': '小类1', 'url': '小类URL?p='},
        {'name': '小类2', 'url': '小类URL?p='},
        ...
    ],
    '大类2': [
        {'name': '小类1', 'url': '小类URL?p='},
        ...
    ],
    ...
}

因此帮忙写下以下代码

def gain_typeurl(type_web_html):

    soup = BeautifulSoup(type_web_html, 'html.parser')

    type_dict={}

    big_types = soup.find_all('ul', class_=lambda x: x is None )
    print(big_types)

    for big_type in big_types:
        big_type_tag = big_type.find(class_="izixun-department-title")
        big_type_name = big_type_tag.text.strip()
        type_dict[big_type_name] = []

        small_type_tags = big_type.find(class_="izixun-department-list")

        small_tags = small_type_tags.find_all('a')

        for small_tag in small_tags:
            small_type_name = small_tag.text.strip()
            small_type_url =  small_tag['href']
            type_dict[big_type_name].append({
                'name': small_type_name,
                'url': small_type_url+"?p=",
            })

    print(type_dict)

    return type_dict

但是每一类在线诊断,不只有一个页面,还是有10页

那我们需要有在url后加一个页面参数

def directory_pachong(url, name):
    create_execl(name)
    for i in range(1, 11):
        turl = url
        turl = "https:"+ turl + str(i)
        print(url)
        directory = get_html(turl)
        directory_url = gain_url(directory)
        z = 0
        for d_url in directory_url:
            html = get_html(d_url)
            data = parse_html(html)
            write_back_execl(data,name)
            z = z + 1
            l = (i - 1) * 90 + z
            print(l)

该函数的目标是:

循环抓取多个分页(10 页)

对每一页中的所有链接进行访问,并从每个链接的页面中提取数据。

提取的数据会被逐页、逐条写入到 execl文件中,最终保存所有爬取的数据。

接下来我们需要获取,该页面下的全部病历url

比如现在我们就进去了心血管内科

def gain_url(directory):

    soup = BeautifulSoup(directory, 'html.parser')

    directory_url=[]

    list_items = soup.find_all('span', class_='fl')

    for item in list_items:
        a_tag = item.find('a')
        url = a_tag.get('href')
        directory_url.append(url)
    print(directory_url)
    return directory_url

gain_url(directory) 函数的目的是从传入的 HTML 页面中提取所有符合条件的 URL 链接,并将它们保存在一个列表中返回。通过查找页面中的 span 标签(类名为 'fl')来定位包含 url 的 a 标签,然后提取出每个 a 标签的 href 属性值。

第四步:保存数据到 execl文件

爬取的数据需要持久化存储。execl文件是一种常用的结构化存储方式,可通过 openpyxl 库实现。

首先来创建excel的

def create_execl(name):
    wb = Workbook()
    ws = wb.active
    ws.title = name
    excel_headers = ["疾病信息", "问诊类型", "病例url", "医生url", "医生简介", "医生擅长", "医生服务质量", "医生建议", "医生与患者交流"]
    ws.append(excel_headers)
    wb.save(name+".xlsx")

创建一个新的 execl,并获取取当前活动工作表。

设置该名称为传入的 name。

添加一行表头,如代码中的就是表头为疾病信息、问诊类型、病例 URL、医生简介等字段。

将该保存为一个 execl文件,文件名为 name.xlsx。

接下来到添加数据到execl

def write_back_execl(data, name):
    #wb = Workbook()
    wb = load_workbook(name+".xlsx")
    ws = wb.active
    ws.append(data)
    wb.save(name+".xlsx")

该函数的目的是:

加载指定的 execl文件(name + ".xlsx")。

获取文件中的活动工作表。

将传入的 data 列表作为一行数据追加到该工作表的末尾。

保存修改后的 execl文件。

完整代码:

import requests
from bs4 import BeautifulSoup
from openpyxl import Workbook

from openpyxl.reader.excel import load_workbook

headers = {
    "cookie": "自己添加",
    "user-agent": "自己添加",
}

def gain_typeurl(type_web_html):

    soup = BeautifulSoup(type_web_html, 'html.parser')

    type_dict={}

    big_types = soup.find_all('ul', class_=lambda x: x is None )
    print(big_types)

    for big_type in big_types:
        big_type_tag = big_type.find(class_="izixun-department-title")
        big_type_name = big_type_tag.text.strip()
        type_dict[big_type_name] = []

        small_type_tags = big_type.find(class_="izixun-department-list")

        small_tags = small_type_tags.find_all('a')

        for small_tag in small_tags:
            small_type_name = small_tag.text.strip()
            small_type_url =  small_tag['href']
            type_dict[big_type_name].append({
                'name': small_type_name,
                'url': small_type_url+"?p=",
            })

    print(type_dict)

    return type_dict

def gain_url(directory):

    soup = BeautifulSoup(directory, 'html.parser')

    directory_url=[]

    list_items = soup.find_all('span', class_='fl')

    for item in list_items:
        a_tag = item.find('a')
        url = a_tag.get('href')
        directory_url.append(url)
    print(directory_url)
    return directory_url

def get_html(url):
    response = requests.get(url, headers=headers)
    return response.text

# 获取所有文本
def get_all_text(element):
    text = ''
    for content in element.contents:
        if content.name is None:
            text += content
        else:
            text += get_all_text(content)  # 递归获取子元素的文本
    return text

def parse_html(html):
    soup = BeautifulSoup(html, 'html.parser')

    old_disease_info = soup.find('p', class_='diseaseinfo') #疾病信息
    disease_info=get_all_text(old_disease_info)
    #print(disease_info)

    header_title = soup.find('h1', class_='header-title')
    text = header_title.get_text(strip=True)
    type = text.split('- ')[-1]#问诊类型
    #print(type)

    meta_og_url = soup.find('meta', property='og:url')
    disease_url = meta_og_url.get('content') #病例url
    #print(disease_url)

    a_tag = soup.find('a', attrs={'target': '_blank', 'rel': 'noreferrer'})
    doctor_url = a_tag.get('href')#医生url
    #print(doctor_url)


    card_info_text = soup.find('div', class_='card-info-text')
    doctor_info=get_all_text(card_info_text) #医生简介
    #print(doctor_info)

    speciality_div = soup.select_one('div[ref="specialityBox"]')
    if speciality_div:
        doctor_speciality = get_all_text(speciality_div)  # 医生擅长
    else:
        doctor_speciality='无说明'
    #print(doctor_speciality)

    doctor_card_service = soup.find('div', class_='doctor-card-service clearfix')
    doctor_service = get_all_text(doctor_card_service) #医生服务质量
    #print(doctor_service)

    suggestions_marginLeft0 = soup.find('section', class_='suggestions')
    doctor_suggestions = get_all_text(suggestions_marginLeft0) #医生建议
    #print(doctor_suggestions)

    msgboard_js_msgboard = soup.find('section', class_='msgboard')
    message= get_all_text(msgboard_js_msgboard) #医生与患者交流
    #print(message)

    return disease_info,type,disease_url,doctor_url,doctor_info,doctor_speciality,doctor_service,doctor_suggestions,message

def create_execl(name):
    wb = Workbook()
    ws = wb.active
    ws.title = name
    excel_headers = ["疾病信息", "问诊类型", "病例url", "医生url", "医生简介", "医生擅长", "医生服务质量", "医生建议", "医生与患者交流"]
    ws.append(excel_headers)
    wb.save(name+".xlsx")

def write_back_execl(data, name):
    #wb = Workbook()
    wb = load_workbook(name+".xlsx")
    ws = wb.active
    ws.append(data)
    wb.save(name+".xlsx")

def directory_pachong(url, name):
    create_execl(name)
    for i in range(1, 11):
        turl = url
        turl = "https:"+ turl + str(i)
        print(url)
        directory = get_html(turl)
        directory_url = gain_url(directory)
        z = 0
        for d_url in directory_url:
            html = get_html(d_url)
            data = parse_html(html)
            write_back_execl(data,name)
            z = z + 1
            l = (i - 1) * 90 + z
            print(l)



if __name__ == "__main__":
    #directory_pachong()
    xurl = "https://www.haodf.com/bingcheng/list.html"
    url_html=get_html(xurl)
    all_type=gain_typeurl(url_html)
    for big_type, small_types in all_type.items():
        print(f"大类: {big_type}")
        for small_type in small_types:
            print(f"\t小类: {small_type['name']} - 链接: {small_type['url']}")
            small_name=small_type['name']
            small_url=small_type['url']
            directory_pachong(small_url,small_name)

存在缺陷

目前好大夫需要登录后才能查看,我之前一个师兄给了有一个vip账号,但是每天只能爬200条十分鸡肋。(就是来跟大家交流学习的作用而已)

一言两语

明天再战科目三了,十分紧张,一定得过啊。