爬虫实战:从外地天气到美食推荐,探索干饭人的世界

今天是第二堂课,我们将继续学习爬虫技术。在上一节课中,我们已经学会了如何爬取干饭教程。正如鲁迅所说(我没说过),当地吃完饭就去外地吃,这启发了我去爬取城市天气信息,并顺便了解当地美食。这个想法永远是干饭人的灵魂所在。

今天我们的目标是学习如何爬取城市天气信息,因为要计划去哪里玩耍,首先得了解天气情况。虽然我们的手机已经装有许多免费天气软件,但是也不妨碍我们学习。

在我们开始学习爬虫技术之前,首先需要找到一个容易爬取数据的天气网站。并不要求特定网站,只要易于爬取的网站即可。毕竟我们目前并不需要爬取特定网站来抢票或抢购商品,我们的主要目的是学习爬虫技术。

天气爬虫

在进行爬虫操作时,如果不确定一个网站是否易于爬取,可以先尝试输入该网站的首页地址,查看能否成功解析出HTML网页。如果解析出来的页面与实际浏览的页面一致,那么说明该网站可能没有设置反爬虫机制;反之,如果解析出来的页面与实际不同,那么该网站很可能设置了反爬虫措施。在学习阶段,建议选择较为容易爬取的网站进行练习,避免过早挑战难度过大的网站。

好的,废话不多说,我们现在就开始抓取该网站上的所有城市信息。

城市列表

天气信息肯定与城市相关,因此几乎每个天气网站都会有城市列表。让我们先来抓取这些城市列表并保存起来,以备后续使用。以下是相应的代码:

# 导入urllib库的urlopen函数
from urllib.request import urlopen,Request
# 导入BeautifulSoup
from bs4 import BeautifulSoup as bf

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'}
req = Request("https://www.tianqi.com/chinacity.html",headers=headers)
# 发出请求,获取html
# 获取的html内容是字节,将其转化为字符串
html = urlopen(req)
html_text = bytes.decode(html.read())
obj = bf(html_text,'html.parser')
# 使用find_all函数获取所有图片的信息
province_tags = obj.find_all('h2')
for province_tag in province_tags:
    province_name = province_tag.text.strip()
    cities = []
    print(province_name)
    next_sibling = province_tag.find_next_sibling()
    city_tags = next_sibling.find_all('a')
    for city_tag in city_tags:
        city_name = city_tag.text.strip()
        cities.append(city_name)
        print(city_name)

在上述操作中,主要的步骤是从城市地址页面中获取信息,对其进行解析以获取省份和城市之间的对应关系。目前仅仅进行了简单的打印输出。

城市天气

在获取城市信息之后,接下来的步骤是根据城市信息获取天气信息。在这里,我们仅考虑直辖市的天气情况,而省份的天气信息获取相比直辖市多了一步省份的跳转。我们暂时不进行省份天气信息的演示。现在,让我们一起来看一下代码:

# 导入urllib库的urlopen函数
from urllib.request import urlopen,Request
# 导入BeautifulSoup
from bs4 import BeautifulSoup as bf

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'}
req = Request(f"https://www.tianqi.com/beijing/",headers=headers)
# 发出请求,获取html
# 获取的html内容是字节,将其转化为字符串
html = urlopen(req)
html_text = bytes.decode(html.read())
obj = bf(html_text,'html.parser')
city_tags = obj.find_all('div',class_='mainWeather')
for city_tag in city_tags:
    a_tags = city_tag.find_all('a', class_=lambda value: value != 'd15')
    for a_tag in a_tags:
        title = a_tag.get('title')
        print(title)
foods = obj.find_all('ul',class_='paihang_good_food')
for food in foods:
    a_tags = food.find_all('a')
    for a_tag in a_tags:
        href = a_tag.get('href')
        print(href)
        title = a_tag.get('title')
        print(title)
weather_info = obj.find_all('dl', class_='weather_info')
for info in weather_info:
    city_name = info.find('h1').text
    date = info.find('dd', class_='week').text
    temperature = info.find('p', class_='now').text
    humidity = info.find('dd', class_='shidu').text
    air_quality = info.find('dd', class_='kongqi').h5.text
    print(f"地点:{city_name}")
    print(f"时间:{date}")
    print(f"当前温度:{temperature}")
    print(humidity)
    print(air_quality)   

以上代码不仅仅把天气解析出来,而且将当前地址的天气和各个城区 的天气以及当地美食都解析了出来。当地美食因为链接是变动的,所以将链接和美食做了响应的映射关系保存。

城市美食

在确定天气适宜的情况下,我们通常都会想了解当地有哪些特色美食,毕竟不能总是吃快餐,特色美食才是我们吃货的灵魂所在。

以下是一个示例代码:

# 导入urllib库的urlopen函数
from urllib.request import urlopen,Request
# 导入BeautifulSoup
from bs4 import BeautifulSoup as bf

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'}
req = Request(f"https://www.tianqi.com/meishi/1737.html",headers=headers)
# 发出请求,获取html
# 获取的html内容是字节,将其转化为字符串
html = urlopen(req)
html_text = bytes.decode(html.read())
obj = bf(html_text,'html.parser')
span_tag = obj.find('span', class_='traffic')
text_content = ''.join(span_tag.stripped_strings)
print(text_content)

在这里,我主要解析了当前美食推荐的原因。实际上,链接应该与之前解析的天气信息相关联,但为了演示方便,我在示例代码中使用了固定值。

包装一下

将以上内容单独制作成小案例确实是一种有效的方式,但将其整合成一个简单的小应用则更具实用性,因为这样可以实现更灵活的交互。让我们一起来看一下最终的代码:

# 导入urllib库的urlopen函数
from urllib.request import urlopen,Request
import urllib,string
# 导入BeautifulSoup
from bs4 import BeautifulSoup as bf
from random import choice,sample
from colorama import init
from os import system
from termcolor import colored
from readchar import  readkey
from xpinyin import Pinyin

p = Pinyin()

city_province_mapping = []

province_sub_weather = []

good_foods = []
FGS = ['green', 'yellow', 'blue', 'cyan', 'magenta', 'red']

def clear():
    system("CLS")

def get_city_province_mapping():
    print(colored('开始搜索城市',choice(FGS)))
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'}
    req = Request("https://www.tianqi.com/chinacity.html",headers=headers)
    # 发出请求,获取html
    # 获取的html内容是字节,将其转化为字符串
    html = urlopen(req)
    html_text = bytes.decode(html.read())
    obj = bf(html_text,'html.parser')
    # 使用find_all函数获取所有图片的信息
    province_tags = obj.find_all('h2')
    for province_tag in province_tags:
        province_name = province_tag.text.strip()
        cities = []

        next_sibling = province_tag.find_next_sibling()
        city_tags = next_sibling.find_all('a')
        for city_tag in city_tags:
            city_name = city_tag.text.strip()
            cities.append(city_name)

        city_province_mapping.append((province_name,cities))


def get_province_weather(province):
    print(colored(f'已选择:{province}',choice(FGS)))
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'}
    req = Request(f"https://www.tianqi.com/{province}/",headers=headers)
    # 发出请求,获取html
    # 获取的html内容是字节,将其转化为字符串
    html = urlopen(req)
    html_text = bytes.decode(html.read())
    obj = bf(html_text,'html.parser')
    city_tags = obj.find_all('div',class_='mainWeather')
    # city_tags = obj.find_all('ul',class_='raweather760')
    province_sub_weather.clear()
    print(colored('解析主要城市中',choice(FGS)))
    for city_tag in city_tags:
        a_tags = city_tag.find_all('a', class_=lambda value: value != 'd15')
        for a_tag in a_tags:
            title = a_tag.get('title')
            province_sub_weather.append(title)
    foods = obj.find_all('ul',class_='paihang_good_food')
    print(colored('解析热搜美食中',choice(FGS)))
    for food in foods:
        a_tags = food.find_all('a')
        for a_tag in a_tags:
            href = a_tag.get('href')
            title = a_tag.get('title')
            good_foods.append((href, title))
    weather_info = obj.find_all('dl', class_='weather_info')
    print(colored('解析完毕',choice(FGS)))
    for info in weather_info:
        city_name = info.find('h1').text
        date = info.find('dd', class_='week').text
        temperature = info.find('p', class_='now').text
        humidity = info.find('dd', class_='shidu').text
        air_quality = info.find('dd', class_='kongqi').h5.text

        print(colored(f"地点:{city_name}",choice(FGS)))
        print(colored(f"时间:{date}",choice(FGS)))
        print(colored(f"当前温度:{temperature}",choice(FGS)))
        print(colored(humidity,choice(FGS)))
        print(colored(air_quality,choice(FGS)))   
def search_food(link):
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'}
    req = Request(f"https://www.tianqi.com{link}",headers=headers)
    # 发出请求,获取html
    # 获取的html内容是字节,将其转化为字符串
    html = urlopen(req)
    html_text = bytes.decode(html.read())
    obj = bf(html_text,'html.parser')
    span_tag = obj.find('span', class_='traffic')
    text_content = ''.join(span_tag.stripped_strings)
    print(colored(text_content,choice(FGS)))

def print_menu():
    for i in range(0, 4, 3):
        names = [f'{i + j}:{city_province_mapping[i + j][0]}' for j in range(3) if i + j < 4]
        print(colored('\t\t'.join(names),choice(FGS)))

def print_food():
    if not good_foods:
        print(colored('请选择城市,才可查看',choice(FGS)))
        return
    for i in range(0, len(good_foods), 3):
        names = [f'{i + j}:{good_foods[i + j][1]}' for j in range(3) if i + j < len(good_foods)]
        print(colored('\t\t'.join(names),choice(FGS)))

def print_hot(weather):
    if not weather:
        print(colored('请选择城市,才可查看',choice(FGS)))
        return
    for i in range(0,len(weather), 3):
        names = [f'{i + j}:{weather[i + j]}' for j in range(3) if i + j < len(weather)]
        print(colored('\t\t'.join(names),choice(FGS)))

get_city_province_mapping()

# get_province_weather('beijing')
# search_food(good_foods[1][0])
init() ## 命令行输出彩色文字
print(colored('已搜索完毕!',choice(FGS)))
print(colored('m:返回首页',choice(FGS)))
print(colored('h:查看当前城区天气',choice(FGS)))
print(colored('f:查看当地美食',choice(FGS)))
print(colored('q:退出天气',choice(FGS)))
my_key = ['q','m','c','h','f']
while True:
    while True:
        move = readkey()
        if move in my_key:
            break
    if move == 'q': ## 键盘‘Q’是退出
        break 
    if move == 'c': ## 键盘‘C’是清空控制台
        clear()
    if move == 'h':  
        print_hot(province_sub_weather)
    if move == 'f':  
        print_food()
        num = int(input('请输入美食编号:=====>'))
        if num <= len(good_foods):
            search_food(good_foods[num][0])
    if move == 'm':
        print_menu()
        num = int(input('请输入城市编号:=====>'))
        if num <= len(city_province_mapping):
            pinyin_without_tone = p.get_pinyin(city_province_mapping[num][0],'')
            get_province_weather(pinyin_without_tone)

按照我的习惯,我通常喜欢在控制台中进行打印输出,这样可以避免不必要的UI依赖。虽然整个过程并不算太复杂,但解析数据确实需要花费一些时间。尽管如此,还是成功完成了天气信息的爬取任务。

总结

在今天的学习中,所涉及的知识点基本延续了上一次的内容,并没有太多新的拓展。主要是对网页进行解析,提取信息并保存,最后根据这些信息来动态改变链接地址,最终完成了一个简单的与用户交互的演示项目。我希望你也能跟着动手实践,尽管这个过程可能会有些痛苦,不过虽然并没有给你的技术水平带来实质性提升,但至少可以拓展你的技术广度。

热门相关:江太太恃宠而骄   江太太恃宠而骄   如意小郎君   龙皇缠身:爱妃,来生蛋!   全民女神:重生腹黑千金