使用sys.argv的Python网页爬虫出现NoneType错误求助
TypeError: 'NoneType' object is not subscriptable 错误修复方案
问题背景
Python新手开发了一个基于sys.argv的volby.cz网站爬虫,计划通过终端传参运行,但执行时触发TypeError: 'NoneType' object is not subscriptable错误,错误定位在url_list函数的all_url = url_part + href.a["href"]代码行。
完整代码
import sys import requests import csv import os from bs4 import BeautifulSoup def main(): url = input_arguments()[0] filename = input_arguments()[1] header = summary_info(url) data = data_scraping(url) save_to_csv(filename, header, data) check_file(filename) def url_list(): url = "https://volby.cz/pls/ps2017nss/ps3?xjazyk=CZ" r = requests.get(url) soup = BeautifulSoup(r.text, "html.parser") list_href = [] for index in range(1, 15): find = soup.find_all(("td", {"headers": f"{index}sa3"})) for href in find: url_part = "https://volby.cz/pls/ps2017nss/" all_url = url_part + href.a["href"] list_href.append(all_url) return list_href def input_arguments(): """ Kontroluje a přijímá argumet URL a název výstupního souboru. """ if len(sys.argv) != 3: print( f"+--------------------------------------------------------------------------+", f"| 1) Jako první argument zadej URL adresu v uvozovkách: |", f'| např.: "https://volby.cz/pls/ps2017" |', f"| 2) Jako druhý argument zadej název souboru pro uložení dat v uvozovkách: |", f'| např.: "vysledky_lipno.csv" |', f"| 3) Celkový vzor: |", f"+--------------------------------------------------------------------------+", f'| election_scraper.py "https://volby.cz/pls/ps2017" "vysledky_lipno.csv" |', f"+--------------------------------------------------------------------------+", sep="\n" ) quit() elif sys.argv[2] in url_list(): print( f"+--------------------------------------------------------------------------+", f"| Jako prní argument zadej URL adresu a jako druhý název souboru. |", f"| Argumenty zadej v uvozovkách a odděl mezerou. |", f"| Viz vzor: |", f"+--------------------------------------------------------------------------+", f'| election_scraper.py "https://volby.cz/pls/ps2017" "vysledky_lipno.csv" |', f"+--------------------------------------------------------------------------+", sep="\n" ) quit() elif sys.argv[1] not in url_list(): print( f"+--------------------------------------------------------------------------+", f"| Tato URL adresa není podporována. |", f"| Zadej podporovanou URL adresu způsobem viz vzor: |", f"+--------------------------------------------------------------------------+", f'| election_scraper.py "https://volby.cz/pls/ps2017" "vysledky_lipno.csv" |', f"+--------------------------------------------------------------------------+", sep="\n" ) quit() elif not sys.argv[2].endswith('.csv'): print( f"+--------------------------------------------------------------------------+", f"| Název souboru musí končit příponou .csv |", f"| Viz vzor: |", f"+--------------------------------------------------------------------------+", f'| election_scraper.py "https://volby.cz/pls/ps2017" "vysledky_lipno.csv" |', f"+--------------------------------------------------------------------------+", sep="\n" ) quit() else: url = sys.argv[1] filename = sys.argv[2] return url, filename def summary_info(url): """ Vypíše názvy stran. """ header = [ "kód obce," "název obce", "voliči v seznamu", "vydané obálky", "platé hlasy", ] first_page = requests.get(url) print( f"Stahuji informace z vybraného URL:", f"{url}", sep="\n" ) soup = BeautifulSoup(first_page.text, "html.parser") first_page_href = soup.find("td", {"class": "cislo"}).a["href"] part_url = "https://volby.cz/pls/ps2017nss/" header_url = part_url + first_page_href second_page = requests.get(header_url) soup = BeautifulSoup(second_page.text, "html.parser") for name in (soup.find_all("td", {"class": "overflow_name"})): header.append(name.text) return header def data_scraping(url): """ ( This function allows download data)Funkce bere kód obce a název obce a přidává data do listu ze stránky vybrané uživatelem. Následně přistupuje přes kód obce ke zbylým datům, která přidává do listu. Jakmile má funkce všechna data pro danou obec/řádek, tak přidává list do listu data. Tento postup se opakuje pro všechny obce. """ first_page = requests.get(url) print( f"stahuji data z vybraného URL:" f"(url", sep="\n" ) soup = BeautifulSoup(first_page.text, "html.parser") first_page_code = soup.find_all("td", {"class": "cislo"}) first_page_names = soup.find_all("td", {"class": "overflow_name"}) if len(first_page_names) == 0: first_page_names = soup.find_all("td", {"headers": "t1sa1 t1sb2"}) first_page_hrefs = [href.a["href"] for href in first_page_code] data = [] part_url = "https://volby.cz/pls/ps2017nss/" for index, result in enumerate(first_page_hrefs, 0): row_list = [] second_url = part_url + result second_page = requests.get(second_url) soup = BeautifulSoup(second_page.text, "html.parser") row_list.append(first_page_code[index].text) row_list.append(first_page_names[index].text) row_list.append((soup.find("td", {"headers": "sa2"}).text).replace('\xa0', '')) row_list.append((soup.find("td", {"headers": "sa3"}).text).replace('\xa0', '')) row_list.append((soup.find("td", {"headers": "sa6"}).text).replace('\xa0', '')) first_candidate_parties = (soup.find_all("td", {"headers": "t1sa2 t1sb3"})) for data_candidate in first_candidate_parties: row_list.append(data_candidate.text.replace('\xa0', '')) second_candidate_parties = (soup.find_all("td", {"headers": "t2sa2 t2sb3"})) for data_candidate in second_candidate_parties: numeric = (data_candidate.text.replace('\xa0', '')) if numeric.isnumeric(): row_list.append(numeric) data.append(row_list) return data def check_file(filename): dash = "-" * len(filename) if filename in os.listdir(): print( f"+---------------------------{dash}+", f"| Data uložena do souboru: {filename} |", f"+---------------------------{dash}+", f"Ukončuji program...", sep="\n", end=("") ) else: print( f"+--------------------{dash}+", f"| Soubor nenalezen: {filename} |", f"+--------------------{dash}+", f"Ukončuji program...", sep="\n", end=("") ) def save_to_csv(filename, header, data): """ Uloží data do csv. """ with open(filename, mode="w", newline="", encoding="utf-8") as data_csv: writer = csv.writer(data_csv) print( f"Ukládám data do vybraného souboru:", f"{filename}", sep="\n" ) writer.writerow(header) for row in data: writer.writerow(row) if __name__ == "__main__": main()
运行参数示例
- 执行命令:
py election_scraper.py - 传入带引号的URL:
"https://volby.cz/pls/ps2017nss/ps311?xjazyk=CZ&xkraj=6&xobec=566403&xvyber=4204" - 传入带引号的CSV文件名:
"vysledky_lipno.csv"
报错信息
Traceback (most recent call last): File "C:\Users\Admin\PycharmProjects\pythonProject3ElectionScraper\election_scraper.py", line 226, in <module> main() File "C:\Users\Admin\PycharmProjects\pythonProject3ElectionScraper\election_scraper.py", line 16, in main url = input_arguments()[0] File "C:\Users\Admin\PycharmProjects\pythonProject3ElectionScraper\election_scraper.py", line 55, in input_arguments elif sys.argv[2] in url_list(): File "C:\Users\Admin\PycharmProjects\pythonProject3ElectionScraper\election_scraper.py", line 33, in url_list all_url = url_part + href.a["href"] TypeError: 'NoneType' object is not subscriptable
错误原因
url_list函数中,遍历soup.find_all(("td", {"headers": f"{index}sa3"}))返回的<td>元素时,部分<td>标签内部未包含<a>标签,导致href.a返回None,此时尝试访问None["href"]就会触发该类型错误。
修复方案
1. 给url_list函数添加空值判断
修改url_list函数,在获取<a>标签的href属性前,先检查<a>标签是否存在:
def url_list(): url = "https://volby.cz/pls/ps2017nss/ps3?xjazyk=CZ" r = requests.get(url) soup = BeautifulSoup(r.text, "html.parser") list_href = [] for index in range(1, 15): find = soup.find_all(("td", {"headers": f"{index}sa3"})) for href in find: # 检查当前<td>是否包含<a>标签 if href.a is not None: url_part = "https://volby.cz/pls/ps2017nss/" all_url = url_part + href.a["href"] list_href.append(all_url) return list_href
2. 优化参数校验逻辑(可选)
当前input_arguments函数多次调用url_list(),会重复发起HTTP请求,建议提前缓存支持的URL列表,减少请求次数:
def input_arguments(): """ Kontroluje a přijímá argumet URL a název výstupního souboru. """ if len(sys.argv) != 3: print( f"+--------------------------------------------------------------------------+", f"| 1) Jako první argument zadej URL adresu v uvozovkách: |", f'| např.: "https://volby.cz/pls/ps2017" |', f"| 2) Jako druhý argument zadej název souboru pro uložení dat v uvozovkách: |", f'| např.: "vysledky_lipno.csv" |', f"| 3) Celkový vzor: |", f"+--------------------------------------------------------------------------+", f'| election_scraper.py "https://volby.cz/pls/ps2017" "vysledky_lipno.csv" |', f"+--------------------------------------------------------------------------+", sep="\n" ) quit() # 提前获取一次支持的URL列表,避免重复请求 supported_urls = url_list() filename = sys.argv[2] url = sys.argv[1] if filename in supported_urls: print( f"+--------------------------------------------------------------------------+", f"| Jako prní argument zadej URL adresu a jako druhý název souboru. |", f"| Argumenty zadej v uvozovkách a odděl mezerou. |", f"| Viz vzor: |", f"+--------------------------------------------------------------------------+", f'| election_scraper.py "https://volby.cz/pls/ps2017" "vysledky_lipno.csv" |', f"+--------------------------------------------------------------------------+", sep="\n" ) quit() elif url not in supported_urls: print( f"+--------------------------------------------------------------------------+", f"| Tato URL adresa není podporována. |", f"| Zadej podporovanou URL adresu způsobem viz vzor: |", f"+--------------------------------------------------------------------------+", f'| election_scraper.py "https://volby.cz/pls/ps2017" "vysledky_lipno.csv" |', f"+--------------------------------------------------------------------------+", sep="\n" ) quit() elif not filename.endswith('.csv'): print( f"+--------------------------------------------------------------------------+", f"| Název souboru musí končit příponou .csv |", f"| Viz vzor: |", f"+--------------------------------------------------------------------------+", f'| election_scraper.py "https://volby.cz/pls/ps2017" "vysledky_lipno.csv" |', f"+--------------------------------------------------------------------------+", sep="\n" ) quit() else: return url, filename
3. 修复其他潜在风险点
代码中summary_info和data_scraping函数也存在直接访问.a["href"]的情况,建议统一添加空值判断,避免触发同类错误:
比如summary_info中的代码:
first_page_href = soup.find("td", {"class": "cislo"}).a["href"]
修改为:
cislo_td = soup.find("td", {"class": "cislo"}) if cislo_td and cislo_td.a: first_page_href = cislo_td.a["href"] else: print("无法找到目标链接,程序退出") quit()
内容的提问来源于stack exchange,提问作者JWchild
相关产品推荐
相关产品推荐

