Python爬取静态网站:以历史天气为例

优采云 发布时间: 2022-06-06 17:06

  Python爬取静态网站:以历史天气为例

  邮箱:

  目录

  4. 完整代码

  5. 相关推文

  温馨提示: 文中链接在微信中无法生效。请点击底部「阅读原文」。或直接长按/扫描如下二维码,直达原文:

  

  数据获取是实证研究的第一步。随着互联网数据的指数级增长,网络数据成为重要且常用的数据源。网络爬虫也因此成为获取数据的重要方式。但是我们通常会觉得爬虫非常复杂,不知道从何下手。为此,本文将通过实际的爬取案例介绍,来帮助大家掌握相关知识。

  1. 静态网页和动态网页

  网页类型包括静态网页和动态网页。简单来说,静态网页是指数据直接存储在网页的 html 中,不论用户是否请求了数据,数据就 “静止” 在那里。动态网页的数据则被 “藏” 起来了,用户每次请求后,动态网页才会有一个向远程数据库请求数据的“动作”,再把数据显示出来,但用户无法直接从网页的 html 中获取数据。

  这里不细说静态网页和动态网页的官方定义,只说两个最明显的区别,以方便大家在分析网页时进行区分:

  直观来说,翻页时网址变化的网站就是静态网站,反之就是动态网站。比如微博评论、bilibili 评论这样一直下滑会一直出现新的数据,但是网址不变的,就是动态网站。

  2. 静态网页爬取的思路

  这次我们先聊聊如何爬取静态网页的数据。由于静态网页结构比较简单,可以直接通过获取网页源代码得到数据,所以爬取比较简单。有了目标网站后,静态网页数据的爬取可以分为四步:

  如果涉及多页的数据,还涉及到分析网址翻页规律和进行循环:

  小提示:多页循环时,可以先爬取单页数据,成功后再循环爬取部分数据 (比如 10 页),没问题再爬取完整数据。

  3. 案例之爬取历史天气

  接下来用一个简单的实战案例,来具体介绍如何通过 Python 爬取静态网页的数据。主要内容包括:

  3.1 分析网页结构

  分析网页结构是爬取数据的第一步,也是重中之重。在本案例中,我们需要爬取「天气网」的历史数据。以北京市 2022 年 3 月天气的「网页」为例,进行网页结构分析。首先,我们需要的数据如下图左所示,包括每天的日期、最高气温、最低气温、天气、风向。

  然后,在浏览器页面右键查看网页源代码,并在源代码中找到对应的数据 (如下图右)。这也是前面提到的静态网页第一个特征:页面上显示的数据都可以在源代码中找到。

  

  小提示:静态网页的结构都比较简单,我们不需要太多精力去分析网页结构,只需要在源代码中验证一下是否有需要的数据即可。

  3.2 请求网页数据

  由于数据都藏在源代码中,我们只需要请求网页内容,即把源代码下载到本地,进行分析。关于网页请求,我们需要用到 Python 爬虫中常用包 requests。

  # 导入模块<br />import requests<br /><br /># 输入网址。这里还是以北京市 2022 年 3 月的天气为例进行单页爬取。<br />url = "https://lishi.tianqi.com/beijing/202203.html"<br /><br /># 伪装一下,让服务器以为是正常浏览,而不是爬虫<br /># 静态网页通常反爬不严格,所以只要通过 User-Agent 伪装成浏览器即可<br />headers = {<br />    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) \<br />     Chrome/77.0.3865.120 Safari/537.36"<br />}<br /><br /># 请求数据,使用 get 方法请求数据<br />response = requests.get(url, headers=headers)<br />response<br />

  如果返回 则代表请求数据成功。如果返回 403 或 404 则说明请求不成功,可能需要检查电脑网络是否通畅、目标网址是否可以正常访问、headers 是否有正确设置等。

  3.3 解析网页数据

  请求成功后,我们要再次回到源代码中,以查看数据结构。

  

  可以看到,在网页源代码中数据是以 html 格式存储的。首先,最外面的 div 标签包裹了整个表格,里面的 div 标签包裹了表头。接着,一个 ul 标签包裹了所有的行内容,其中每一行是一个 li 标签,每列具体数据是 div 标签。

  因此,我们需要的是每个 li 标签里所有 div 标签里的数据。这里,我们使用 Python 中 bs4 包的 BeautifulSoup 进行网页数据解析。

  # 导入模块<br />from bs4 import BeautifulSoup<br /><br />soup = BeautifulSoup(response.text, "html.parser")  # 由于是通过 html 格式存储的,所以用 “html.parser” 进行解析<br />data_table = soup.find('ul', class_="thrui").find_all("li")  # 找到包裹表内容的 ul 标签,找到里面所有的 li 标签<br /><br />weather_list = []  # 构造空列表以存储数据<br />for li in data_table[1:]:  # 循环获取每行的数据(li 标签)<br />    th_list = li.find_all('div')  # 获取每行的每个数据 (li 标签下的 div 标签)<br />    weather = {<br />        'date': th_list[0].get_text(),  # 获取第一个 div 标签中的内容,命名为 “date”<br />        'temp_high': th_list[1].get_text(),<br />        'temp_low': th_list[2].get_text(),<br />        'weather': th_list[3].get_text(),<br />        'wind': th_list[4].get_text(),<br />        'url': response.url  # 爬取时通常可以顺便保存一下当页的网址,方便溯源和排查错误<br />    }  # 每行数据存储在一个字典中<br />    weather_list.append(weather)  # 所有行的数据存入一个列表中<br />

  小提示:在爬取表格型数据时,如果列数很多,像上面一样单个获取再存储就比较麻烦。这里再提供一种适用于爬取表格型数据的更简洁的方式,需要用到 Python 中另一个强大的模块 numpy。

  import numpy as np<br />weather_list = []<br />for li in data_table[1:]:     <br />    th_list = li.find_all('div')<br />    for th in th_list:<br />        s = th.get_text()  # 循环获取 div 标签数据<br />        weather_list.append("".join(s.split()))  # 直接把获取的 div 标签全部存在一个列表中<br />result = np.array(weather_list).reshape(-1, 5)   # 通过 numpy 直接转为多行 5 列的数据表<br /># 最后再转化为数据框并重命名列即可<br /># 这种方式虽然方便,但可能不太直观,也可能出错(比如有的数据不全,就容易错位)<br /># 此外,还有很多不同的解析数据的方式,可以多去尝试<br />

  3.4 储存爬取数据

  把数据解析好之后,数据就可以通过更加结构化的形式进行存储,通常可以使用 txt、excel、csv 等格式。由于无格式、比 txt 更直观、可以用 excel 打开,一般比较推荐用 csv 格式储存。

  # 导入模块<br />import csv<br /><br /># 保存数据的文件路径<br />save_path = 'weather.csv'<br /><br /># 将数据写入 csv<br />with open(save_path, 'a', newline='', encoding='utf-8') as fp:<br />    csv_header = ['date', 'temp_high', 'temp_low', 'weather', 'wind', 'url']  # 设置表头,即列名<br />    csv_writer = csv.DictWriter(fp, csv_header)  <br />    if fp.tell() == 0:<br />        csv_writer.writeheader()  # 如果文件不存在,则写入表头;如果文件已经存在,则直接追加数据不再次写入表头。<br />    csv_writer.writerows(weather_list)  # 写入数据<br />

  小提示:爬取时,建议爬一页存一页。如果完全爬取完再一次写入,很可能会遇到循环爬取过程中出错,导致已爬取的数据无法成功储存。这里写入 csv 时用的编码格式是 utf-8,所以打开数据时也要用对应的编码格式,否则可能会出现乱码。

  3.5 循环爬取数据

  通常,我们需要的不仅仅是单页的数据,而是网站上的所有或大部分数据,所以需要多页循环爬取。在成功爬取一页数据之后,多页爬取就很简单了。只需要分析网址的变化规律,生成所有需要的网址,再把单页爬取重复多次就可以了。

  首先,来分析一下网址的变化规律。

  bejing<br />https://lishi.tianqi.com/beijing/202203.html<br />https://lishi.tianqi.com/beijing/202202.html<br />https://lishi.tianqi.com/beijing/202201.html<br />https://lishi.tianqi.com/beijing/202112.html<br /><br />shanghai<br />https://lishi.tianqi.com/shanghai/202203.html<br />https://lishi.tianqi.com/shanghai/202202.html<br />

  可以看到,网址在变化。这也是前面提到静态网页的另一个特征,网址不变数据不 “动”,数据要 “动” 网址就变。 回到例子中,只有两个地方在变化。一个是城市变化时,网址的城市代码部分变化,这里是城市的汉语拼音。另一个是时间变化时,网址的时间代码部分变化,这里是年月的 6 位数字。

  知道了网址的变化规律后,我们就可以通过循环来爬取多页数据。这里有两种思路,一种是先根据网址变化规律,一次性生成所有要爬取的网址,再在网址中循环获取数据。另一种是根据网址变化规律,生成一个网址,爬取一个网址,不断循环。两种方法都可以,这里使用第一种,先生成所有网址。

  url_pattern = 'https://lishi.tianqi.com/{}/{}.html'  # 网址的基本结构,有变化的两个部分用 {} 替代,后面循环补充<br /><br />city_list = ['beijing', 'shanghai']      # 构造需要爬取的城市的列表<br />years = [x for x in range(2020, 2022)]   # 使用列表生成式生成年份列表<br />months = [str(x).zfill(2) for x in range(1, 13)] # 生成月份列表,zfill 函数补充两位数<br />month_list = [str(year) + str(month) for year in years for month in months]  # 年月循环拼在一起<br /><br />url_list = []              # 空列表用于存储所有网址<br />for c in city_list:       # 先循环城市<br />    for m in month_list:  # 再循环时间<br />        url_list.append(url_pattern.format(c, m))  # 通过 format 函数生成网址<br />

  生成了所有的网址后,把前面单页爬取循环到不同的网址下,就可以获取所有的数据了。

  4. 完整代码

  在实际过程中,通常会把每个需要重复操作的步骤都构造成函数,方便在循环中调用。这里的完整代码是通过构造函数的方式来进行的,函数的主体部分就是前面每个步骤的具体代码。

  # -*- coding: utf-8 -*-<br /># Author: W.Y.<br /># Email: wangyingchn@outlook.com<br /># Date: 2022/4/4<br /><br /># 导入模块<br />import csv  # 用于存储数据<br />import time  # 用于时间间隔避免过频繁的请求<br />import requests  # 用于请求数据<br />from bs4 import BeautifulSoup  # 用于解析数据<br /><br /># 请求数据<br />def get_response(url):<br />    # 伪装一下,让服务器以为是正常浏览,而不是爬虫。<br />    # 静态网页通常反爬不严格,所以只要通过 User-Agent 伪装成浏览器即可。<br />    headers = {<br />        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \<br />        (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"<br />    }<br />    # 请求数据。使用 get 方法请求数据<br />    response = requests.get(url, headers=headers)<br />    return response<br /><br /># 解析数据<br />def parse_data(response):<br />    soup = BeautifulSoup(response.text, "html.parser")  # 由于是通过 html 格式存储的,所以用 “html.parser” 进行解析<br />    data_table = soup.find('ul', class_="thrui").find_all("li")  # 找到包裹表内容的 ul 标签,找到里面所有的 li 标签<br />    weather_list = []  # 构造空列表以存储数据<br />    for li in data_table[1:]:  # 循环获取每行的数据(li 标签)<br />        th_list = li.find_all('div')  # 获取每行的每个数据 (li 标签下的 div 标签)<br />        weather = {<br />            'date': th_list[0].get_text(),  # 获取第一个 div 标签中的内容,命名为 “date”<br />            'temp_high': th_list[1].get_text(),<br />            'temp_low': th_list[2].get_text(),<br />            'weather': th_list[3].get_text(),<br />            'wind': th_list[4].get_text(),<br />            'url': response.url  # 爬取时通常可以顺便保存一下当页的网址,方便溯源和排查错误<br />            # 'city': response.url.split('/')[3]  # 如果需要增加一列城市,也可以通过 url 来获取<br />        }   # 每行数据存储在一个字典中<br />        weather_list.append(weather)  # 所有行的数据存入一个列表中<br />    return weather_list<br /><br /># 储存数据<br />def save_data(weather_list, save_path):<br />    with open(save_path, 'a', newline='', encoding='utf-8') as fp:<br />        csv_header = ['date', 'temp_high', 'temp_low', 'weather', 'wind', 'url']  # 设置表头,即列名<br />        csv_writer = csv.DictWriter(fp, csv_header)<br />        if fp.tell() == 0:<br />            csv_writer.writeheader()  # 如果文件不存在,则写入表头;如果文件已经存在,则直接追加数据不再次写入表头<br />        csv_writer.writerows(weather_list)  # 写入数据<br /><br /># 构造网址<br />def generate_urls():<br />    url_pattern = 'https://lishi.tianqi.com/{}/{}.html'  # 网址的基本结构,有变化的两个部分用 {} 替代,后面循环补充<br />    city_list = ['beijing', 'shanghai']  # 构造需要爬取的城市的列表<br />    years = [x for x in range(2020, 2022)]  # 使用列表生成式生成年份列表<br />    months = [str(x).zfill(2) for x in range(1, 13)]  # 生成月份列表,zfill 函数补充两位数<br />    month_list = [str(year) + str(month) for year in years for month in months]  # 年月循环拼在一起<br />    url_list = []  # 空列表用于存储所有网址<br />    for c in city_list:  # 先循环城市<br />        for m in month_list:  # 再循环时间<br />            url_list.append(url_pattern.format(c, m))  # 通过 format 函数生成网址<br />    return url_list<br /><br /># 定义爬取函数<br />def crawler(url, save_path):<br />    response = get_response(url)    # 请求数据<br />    results = parse_data(response)  # 解析数据<br />    save_data(results, save_path)   # 存储数据<br />    print(f'成功爬取数据:{url}')<br /><br />if __name__ == '__main__':<br />    urls = generate_urls()     # 构造所有网址<br />    save_file = 'weather.csv'  # 保存数据的文件路径<br />    for u in urls:     # 在网址中循环<br />        time.sleep(2)  # 每次爬取休息 2 秒,以免太过频繁的请求<br />        crawler(u, save_file)  # 进行爬取<br />

  5. 相关推文

  Note:产生如下推文列表的 Stata 命令为:

  lianxh 爬虫, m

  安装最新版 lianxh 命令:

  ssc install lianxh, replace

  专题:Python-R-Matlab

  

  连享会 ·

  

  

  New! Stata 搜索神器:lianxh 和 songbl

  搜: 推文、数据分享、期刊论文、重现代码 ……

  安装:

  . ssc install lianxh

  . ssc install songbl

  使用:

  . lianxh DID 倍分法

  . songbl all

  

  关于我们

  

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线