编程新手请教:如何合理搭建Django网页爬虫可视化项目目录结构?
针对Django爬虫+可视化项目的目录结构建议与代码优化
Hey there! 你的思路其实已经很靠谱了——把爬虫、数据模型、可视化逻辑拆分到不同文件是Django项目解耦的关键,这能让代码更易维护和扩展。我来给你细化一下推荐的目录结构,再针对你当前的代码提些小建议:
推荐的项目目录结构(以你的league_app为例)
假设你的项目根目录是football_scraper,应用是league_app,推荐结构如下:
football_scraper/ ├── league_app/ │ ├── __init__.py │ ├── models.py # 数据模型,存储爬取到的球员数据 │ ├── views.py # 可视化视图,处理页面渲染、图表数据传递 │ ├── scrapers/ # 新建爬虫模块目录,把爬虫逻辑归类 │ │ ├── __init__.py │ │ ├── player_scraper.py # 专门处理球员数据爬取的代码(替代你想的scrape.py) │ │ └── ... # 如果有其他类型爬虫,比如球队数据,可新增文件 │ ├── templates/ │ │ └── league_app/ │ │ └── player.html │ └── static/ # 存放可视化相关的JS/CSS(比如Chart.js) │ └── league_app/ │ ├── js/ │ └── css/ └── manage.py
各模块作用说明
- scrapers目录:把爬虫代码按功能拆分(比如球员、球队、赛事),比单个
scrape.py更清晰,后续扩展新爬虫时不会让文件臃肿。你可以在player_scraper.py里写一个类或者函数来处理球员数据的爬取。 - models.py:定义
Player和PlayerStats这类模型,把爬取到的数据持久化到数据库,而不是每次请求都重复爬取(减少重复请求,提升性能,还能避免被目标网站封禁IP)。 - views.py:专注于处理用户请求、从数据库获取数据、准备可视化需要的上下文,必要时调用爬虫更新数据,最后渲染模板。
你的views.py代码优化建议
先指出你当前代码里的几个小问题:
- 导入了
BeautifulSoup但没有初始化soup对象 - 直接在视图里写爬虫逻辑会导致每次请求页面都爬取一次,效率极低
- 缺少异常处理,请求失败或找不到指定标签时会直接抛出错误
优化后的代码拆分示例
1. 把爬虫逻辑移到scrapers/player_scraper.py
# league_app/scrapers/player_scraper.py import requests from bs4 import BeautifulSoup def get_player_stats(first_name, last_name): try: # 统一转大写适配目标网站URL规则 last_name_first_letter = last_name[0].upper() last_name_first_four = last_name[:4].upper() first_name_first_two = first_name[:2].upper() url = f'https://www.pro-football-reference.com/players/{last_name_first_letter}/{last_name_first_four}{first_name_first_two}00.htm' # 加超时和请求头,避免被拦截 headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'} page = requests.get(url, headers=headers, timeout=10) page.raise_for_status() # 主动抛出HTTP错误 soup = BeautifulSoup(page.content, 'html.parser') week_number = 251 stats_row = soup.find("tr", {"id": f"stats.{week_number}"}) if not stats_row: return None stats_relevant = ['pass_yds', 'pass_td', 'rush_yds'] stat_list = [] for stat in stats_relevant: stat_elem = stats_row.find("td", {"data-stat": stat}) # 处理缺失数据的情况 if stat_elem and stat_elem.text.strip(): stat_list.append(int(stat_elem.text.strip())) else: stat_list.append(0) return stat_list except Exception as e: print(f"爬取错误: {str(e)}") return None
2. 在models.py里定义数据模型
# league_app/models.py from django.db import models class Player(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) created_at = models.DateTimeField(auto_now_add=True) class PlayerStats(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE) pass_yds = models.IntegerField() pass_td = models.IntegerField() rush_yds = models.IntegerField() week_number = models.IntegerField() updated_at = models.DateTimeField(auto_now=True)
3. 修改views.py,专注于视图逻辑
# league_app/views.py from django.shortcuts import render from .scrapers.player_scraper import get_player_stats from .models import Player, PlayerStats def PlayerDetailView(request): first_name = 'Drew' last_name = 'Brees' week_number = 251 # 先尝试从数据库获取已存储的数据 player, created = Player.objects.get_or_create(first_name=first_name, last_name=last_name) stats = PlayerStats.objects.filter(player=player, week_number=week_number).first() if not stats: # 数据库无数据时调用爬虫 stat_list = get_player_stats(first_name, last_name) if stat_list: stats = PlayerStats.objects.create( player=player, pass_yds=stat_list[0], pass_td=stat_list[1], rush_yds=stat_list[2], week_number=week_number ) else: stat_list = [0, 0, 0] # 爬取失败时返回默认值 else: # 从数据库读取数据 stat_list = [stats.pass_yds, stats.pass_td, stats.rush_yds] context = {'player_name': f"{first_name} {last_name}", 'stats': stat_list} return render(request, 'league_app/player.html', context)
额外实用建议
- 定时更新数据:如果需要定期刷新数据,可以用
django-crontab或者Celery实现定时爬取任务,不要每次请求都触发爬虫。 - 可视化实现:在模板
player.html里可以用Chart.js这类轻量库,把stat_list传递给JS后生成柱状图或折线图。 - IP封禁规避:给爬虫请求加随机延迟、使用代理池,避免频繁请求目标网站导致IP被封禁。
内容的提问来源于stack exchange,提问作者Jan F.
相关产品推荐
相关产品推荐

