首页 文章 精选 留言 我的

精选列表

搜索[python],共10000篇文章
优秀的个人博客,低调大师

用 Python 看清岗位结构变化:数据采集、入库与可视化

岗位结构在怎么变,不必只转述别人的结论,自己动手算一遍更可靠。公开报告可作参照(如 LinkedIn 就业报告、猎聘《2026 FDE 人才供需趋势洞察报告》),本文聚焦方法:采集 → 入库 → 分析 → 可视化。 ## 一、数据采集 合规前提:遵守目标站点 robots 协议与服务条款,控制请求频率,仅用于个人学习研究。 ``` import csv import time import requests def fetch_page(keyword: str, page: int) -> list: """按关键词拉取一页职位列表,接口地址请替换为合规数据源。""" resp = requests.get( "https://你的数据源地址/api/jobs", params={"kw": keyword, "page": page}, headers={"User-Agent": "research-script/1.0"}, timeout=10, ) resp.raise_for_status() return resp.json()["items"] rows = [] for kw in ["智能体", "数字人", "AI运营", "客服", "文案"]: for p in range(1, 4): rows += fetch_page(kw, p) time.sleep(2) with open("jobs.csv", "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter( f, fieldnames=["title", "salary_min", "salary_max", "city", "publish_date"] ) writer.writeheader() writer.writerows(rows) ``` ## 二、入库 ``` CREATE TABLE job_posting ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, category TEXT, salary_min REAL, salary_max REAL, city TEXT, publish_date TEXT ); CREATE INDEX idx_category ON job_posting(category); ``` ## 三、归类与分析 ``` import pandas as pd df = pd.read_csv("jobs.csv") AI_KEYS = ["智能体", "数字人", "AI", "大模型", "提示词"] def classify(title: str) -> str: return "AI应用类" if any(k in title for k in AI_KEYS) else "传统运营类" df["category"] = df["title"].map(classify) df["salary_mid"] = (df["salary_min"] + df["salary_max"]) / 2 summary = df.groupby("category").agg( 职位数=("title", "count"), 薪资中位数=("salary_mid", "median"), ) print(summary) ``` ## 四、可视化 ``` import matplotlib.pyplot as plt summary["职位数"].plot(kind="bar") plt.ylabel("职位数") plt.tight_layout() plt.savefig("jobs.png", dpi=150) ``` 若数据源提供投递量,可进一步计算供需比 = 投递量 ÷ 职位数。文章中的结论应由你跑出的真实数据回填,以上代码只负责把方法跑通。

优秀的个人博客,低调大师

Python 爬虫技术栈:从静态页面提取到工程化抓取

爬虫的技术选型往往取决于目标页面的渲染方式与抓取规模。用一个代码案例贯穿始终并不现实:用 `requests` 抓取 JavaScript 动态渲染的页面会返回空壳 HTML,而用 Scrapy 去处理单页几个字段的临时任务又显得过重。本文按“静态页面提取 → 大规模工程化抓取 → 动态渲染处理”的递进路线展开,每个阶段附可运行的代码示例,最后补充反爬应对的实操策略。 --- ## 一、静态页面:requests + BeautifulSoup 的最小可行组合 对于服务端直接返回完整 HTML 的静态页面,`requests` 负责 HTTP 通信,`BeautifulSoup` 负责 DOM 解析,是最低学习成本的选择。`requests` 本身不解析页面,也不执行 JavaScript,它只拿回服务器返回的原始内容。这一限制决定了它只适合初始 HTML 中已包含目标数据的场景。 以练习站点为例,抓取首页所有书籍的标题与价格: ``` import requests from bs4 import BeautifulSoup def fetch_books(url: str) -> list[dict]: 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" ) } resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") books = [] for card in soup.select("article.product_pod"): title_tag = card.select_one("h3 a") price_tag = card.select_one("p.price_color") if not title_tag or not price_tag: continue books.append({ "title": title_tag.get("title", "").strip(), "price": price_tag.get_text(strip=True), "url": "https://books.ssjdxm.com/" + title_tag.get("href", ""), }) return books if __name__ == "__main__": for b in fetch_books("https://books.ssjdxm.com/"): print(b) ``` 这里有两个容易忽略的细节。其一,`raise_for_status()` 在状态码非 2xx 时抛出异常,避免后续解析空内容时产生难以定位的错误。其二,`select_one` 在未命中时返回 `None`,后续调用 `.get()` 或 `.get_text()` 会直接报 `AttributeError`,显式判空比事后调试更省时间。`BeautifulSoup` 对畸形 HTML 的容错性较好,适合结构不那么规整的页面;如果追求解析速度且 HTML 结构稳定,可以换用 `lxml` 作为解析器后端,XPath 表达力也更强。 --- ## 二、工程化抓取:Scrapy 的异步调度与结构化管道 当目标从“抓一页”变成“抓一万页”,手写循环加 `time.sleep` 的脚本在并发、去重、失败重试、数据导出上都会变得难以维护。`Scrapy` 把这些环节拆成独立组件:引擎负责调度,下载器负责请求,Spider 负责解析,Item Pipeline 负责清洗与存储。它的异步设计基于 Twisted,在同等网络条件下吞吐量显著高于同步脚本。 下面是一个完整的 Scrapy Spider,抓取全部书籍,并跟随“Next”按钮翻页: ``` import scrapy class BooksSpider(scrapy.Spider): name = "books_full" allowed_domains = ["wap.njjiamei.com"] start_urls = ["http://wap.njjiamei.com/"] def parse(self, response): for card in response.css("article.product_pod"): yield { "title": card.css("h3 a::attr(title)").get(default="").strip(), "price": card.css("p.price_color::text").get(default="").strip(), "detail_url": response.urljoin( card.css("h3 a::attr(href)").get() ), } next_page = response.css("li.next a::attr(href)").get() if next_page: yield response.follow(next_page, callback=self.parse) ``` 运行方式:`scrapy crawl books_full -o books.json`。`-o` 参数会根据文件后缀自动选择导出格式,JSON、JSON Lines、CSV 均可。`response.follow` 内部会调用 `urljoin` 处理相对路径,同时 Scrapy 自带的请求去重机制会阻止同一 URL 被重复抓取,翻页场景下不需要额外写去重逻辑。 Scrapy 的代价是学习曲线。项目结构、`settings.py` 中的并发与延迟配置、中间件机制,都需要一定时间熟悉。如果任务只是定期抓取少量页面,`requests + BeautifulSoup` 的脚本配合 cron 定时执行可能更直接。 --- ## 三、动态渲染:Playwright 处理 JavaScript 生成的页面 当目标数据由 JavaScript 在浏览器端渲染时,`requests` 拿到的 HTML 中只有空容器或占位脚本。`Playwright` 驱动真实浏览器引擎(Chromium、Firefox、WebKit),等待页面渲染完成后再提取内容。与 `Selenium` 相比,Playwright 的 API 设计更现代,自动等待机制减少了因元素未就绪而导致的随机失败。 以下示例等待书籍列表渲染完成后提取数据: ``` from playwright.sync_api import sync_playwright def scrape_dynamic(url: str) -> list[dict]: results = [] with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto(url, wait_until="networkidle") page.wait_for_selector("article.product_pod") cards = page.query_selector_all("article.product_pod") for card in cards: title_el = card.query_selector("h3 a") price_el = card.query_selector("p.price_color") results.append({ "title": title_el.get_attribute("title") if title_el else "", "price": price_el.inner_text() if price_el else "", }) browser.close() return results ``` `wait_until="networkidle"` 等待网络请求基本停止,`wait_for_selector` 确保目标元素出现在 DOM 中。Playwright 也支持拦截网络请求、复用浏览器上下文(context)来保持登录态,适合需要交互(点击、滚动、填表)才能加载数据的场景。代价是资源消耗:每个浏览器实例占用数百 MB 内存,吞吐量远低于纯 HTTP 方案,不适合大规模并发抓取。 --- ## 四、反爬应对的实操要点 即使代码逻辑正确,请求仍可能被 403 或虚假数据拦截。以下是可落地的策略,不涉及 JS 逆向等深度对抗。 **请求头伪装是第一道门槛。** Scrapy 默认的 User-Agent 是 `Scrapy/版本号`,许多站点会直接拒绝。在 `settings.py` 中替换为常见浏览器 UA 是最低成本的修复。`requests` 同理,至少携带 `User-Agent` 和 `Accept-Language`。 **频率控制不能靠固定间隔。** 固定 `time.sleep(0.1)` 产生的请求间隔过于规律,反而容易触发基于行为特征的风控。使用随机区间更接近人类浏览节奏: ``` import random, time def polite_delay(): time.sleep(random.uniform(0.8, 2.5)) ``` 在 Scrapy 中对应 `DOWNLOAD_DELAY` 配合 `RANDOMIZE_DOWNLOAD_DELAY = True`(默认开启)。如果并发开到 32 仍然秒被封,问题通常不在代码,而在于请求密度本身已经超出了目标站点的容忍阈值。 **代理不是万能药,但不换 IP 一定走不远。** 单一 IP 的请求量累积到一定程度后,即使频率很低也可能被信誉系统降权。代理轮换需要配合有效性检测,免费代理池的可用率极低,不值得投入时间调试。对于学习用途,控制总量、降低频率比折腾代理更实际。 **遵守 robots.txt 是底线。** Scrapy 的 `ROBOTSTXT_OBEY = True` 是默认配置,不要为了抓取本被禁止的路径而关闭它。目标站点的服务条款和版权声明同样需要关注,爬虫的技术能力不构成绕过这些约束的理由。 --- ## 选型速查 |场景|推荐方案|关键理由| | --- | --- | --- | |静态页面、少量字段、快速验证|requests + BeautifulSoup|代码直观,依赖少,调试成本低| |多页/多层级抓取、需要持久化与去重|Scrapy|异步调度、内置去重与导出,项目结构可维护| |数据由 JS 渲染、需要交互|Playwright(或 Selenium)|真实浏览器环境,能拿到动态生成的 DOM| |大规模抓取且对性能敏感|Scrapy + lxml 解析器|异步吞吐 + C 库解析速度| 爬虫的工程复杂度应当与任务规模匹配。一个只跑一次、抓取几百条数据的脚本,不值得引入 Scrapy 的完整项目结构;而需要持续运行、增量更新、多站点管理的采集任务,从 Scrapy 开始会比从脚本逐步演化更省力。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

用户登录
用户注册