Skip to content

Python 爬虫

爬虫是什么?

我所理解的爬虫,本质是针对网络中直接对外呈现的内容,梳理其数据传输链路与展示规律,进而解析提取其中核心价值数据的技术手段。

例子一:

  • 若我想快速掌握小红书每日的内容推送逻辑,可先查看其首页 —— 内容以列表形式呈现,每个卡片对应一条笔记内容。
  • 我可以通过爬虫获取每个卡片的核心信息(如标题 Title 和封面图 Cover Image),再结合大数据分析、深度学习等方法,挖掘这些内容的价值,或是反推自己在小红书平台的用户画像特征。

例子二:

  • 有时会遇到这类情况:某个网站的界面(UI)设计不符合使用习惯,但其承载的数据却无可替代;同时该网站未开放 API 接口,无法通过正规渠道获取数据源,也就无法自行搭建 Web 前端或移动应用,来更高效、直观地分析数据并辅助决策。
  • 这时就可以借助爬虫工具,在合规前提下抓取网站前端呈现的结构化、有规律的数据,并将其存储到本地介质(如文件、SQLite 数据库等)。
  • 后续可将这些数据接入自研的 Web 项目中(既可以是 Python Web + Jinja2 这类前后端不分离的架构,也可以基于 Spring Boot 封装为 Restful API,再对接 Vue/React 前端展示 —— 具体技术选型可灵活调整,并非核心)。
  • 结合自定义的 UI 设计,能大幅提升大脑对数据的分析效率,辅助做出更精准的决策。

那么针对这个工具,就是我们要使用的爬虫了。

为什么用Python

  • 从本质上看,爬虫是通过自动化方式模拟用户访问网站 / 移动应用,再按照预设的规则提取指定内容的程序(这是爬虫的基础定义;更前沿的爬虫会结合深度学习实现数据的自主学习与提取,这部分暂不展开)。
  • 从技术实现角度,任何编程语言都能完成爬虫开发,比如 Java、JavaScript 等均具备相应能力。
  • 但实际应用中存在关键差异:Java、JavaScript 等语言的爬虫生态,没有 Python 这么完善。Python 拥有 Beautiful Soup 这类功能成熟、易用性极高的爬虫专用库,能大幅降低开发成本。
  • 更重要的是,Python 语法简洁易懂:同样的功能,Java 可能需要数十行代码实现,而 Python 调用第三方库往往几行就能完成。且爬虫任务大多属于脚本级需求,而非大型工程化开发 —— 它通常无需搭建庞大的服务集群,对工程化规范的要求相对宽松,往往一段 50 行以内的代码就能完成完整的爬取、解析、存储流程。
  • 因此,尽管爬虫的实现不局限于 Python,但凭借生态优势和开发效率,Python 成为了业界实现爬虫任务的主流选择。

Python 爬虫经常使用的几个核心工具

ToolsEffectedScene
requests发起 HTTP/HTTPS 请求爬取静态网页、接口数据
BeautifulSoup4解析 HTML/XML 提取数据静态网页数据解析
lxml高性能 HTML/XML 解析复杂网页解析
Scrapy爬虫框架(整合请求 / 解析 / 存储)大规模、结构化爬取
Selenium/Playwright模拟浏览器行为爬取动态渲染网页
  • 基础的爬虫步骤,就是可以通过requests实现一个网络请求的内容。
    • 这里就需要注意一下网站方可能会做的一些tricks,比如你缺少Cookie、Content-Type、Auth等等内容,它会返回错误的状态给你,那么就需要去真实请求中,拿到这些数据,再通过Python模拟请求发送出去
    • 比如可以通过Edge、Chrome这些浏览器,去请求网页,然后通过F12查看每一个请求携带的Header,找到欠缺的内容,然后在Python上通过Header传递上去。
  • 拿到数据之后:
    • 如果是Restful API的内容,我们会得到一个JSON内容(通常这类的数据,就是交给分离式的前端项目去fetch,或者交给移动端通过Retrofit2去解析)
    • 而如果是直接呈现给用户观看到的Web内容,大抵就是一个html内容。这个时候,就可以去分析得到的这个HTML网页,如何按照一个规律性的角度,把核心的数据,拿出来,存起来,然后进一步去分析数据背后的商业逻辑。

实际案例

  • 最近在家很无聊,合法的东西,我都不是很感兴趣。
  • 黄赌毒里面,毒品我肯定是不敢碰的。
  • 赌博这个角度的话,我最近在做另外一个项目,使用Python Web + Bootstrap搭建一个德州扑克和一个国际象棋的单机游戏。自己在家里玩单机版本的棋牌,挺有意思的。
  • 所以对于爬虫这一块来讲,留下的不合法空间,就只有黄色了。
  • 于是,我在想,要不使用爬虫去爬黄色网站的信息,然后把它封面图下载下来。

注意:实操此案例,需要保证网络可以访问Google,可以采用ShadowsRock、V2RayNG、ClashX等等方案,也可以参考路由器翻墙方案

我所选择要去爬取资源的网站是:JavBus

  • 因为他们的url格式,真的很让人绷不住的简单,他们的格式就是:

  • 它的Cover Image的区域,是在:

html
<div class="col-md-9 screencap">
    <a class="bigImage" href="/pics/cover/6qah_b.jpg">
      <img src="/pics/cover/6qah_b.jpg" title="KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ">
    </a>
</div>
  • 而针对他们的样品图片这个区域,则是:
html
<div id="sample-waterfall">
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-1.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_1.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 1">
    </div>
  </a>        
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-2.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_2.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 2">
    </div>
  </a>        
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-3.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_3.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 3">
    </div>
  </a>        
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-4.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_4.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 4">
    </div>
  </a>       
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-5.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_5.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 5">
    </div>
  </a>        
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-6.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_6.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 6">
    </div>
  </a>        
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-7.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_7.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 7">
    </div>
  </a>        
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-8.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_8.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 8">
    </div>
  </a>        
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-9.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_9.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 9">
    </div>
  </a>        
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-10.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_10.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 10">
    </div>
  </a>       
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-11.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_11.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 11">
    </div>
  </a>        
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-12.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_12.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 12">
    </div>
  </a>       
  <a class="sample-box" href="https://pics.dmm.co.jp/digital/video/118kbi00002/118kbi00002jp-13.jpg">
    <div class="photo-frame">
      <img src="/pics/sample/6qah_13.jpg" title="KBI-002 KANBi専属出演第1弾!旦那を忘れる程 汗だく汁だくで絡み合う 濃厚接吻性交3本番 舌を絡ませ抱き合いながら絶頂に達する密着性交! 織笠るみ - 樣品圖像 - 13">
    </div>
  </a>            
</div>

这你妈的这种格式简直就是爬虫初学者的天堂了,都不需要使用Selenium/Playwright这两个东西了,直接一个requests + BeautifulSoup简单解析就行了。

我不去爬你,这简直是天理难容了。

所以,具体的Python代码如下所示

  • 配置文件
  • series_image_cover_config.py
python
series = "KBI"
start_code = 1
end_code = 100
  • 实际解析以及下载、程序主入口的内容
  • series_image_cover_download.py
python
import os
import asyncio
import aiohttp
from bs4 import BeautifulSoup
import urllib3

from series_image_cover_config import *

# 禁用 InsecureRequestWarning 警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

host = "https://www.javbus.com"

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.9",
}

async def download_image_cover(session: aiohttp.ClientSession, name: str, series: str):
    URL = f"{host}/{name}"
    SAVE_DIR = f"./{series}"
    os.makedirs(SAVE_DIR, exist_ok=True)

    try:
        async with session.get(URL, timeout=aiohttp.ClientTimeout(total=10), ssl=False) as response:
            if response.status != 200:
                print(f"[{name}] 页面不存在,跳过")
                return
        
            html = await response.text()
            soup = BeautifulSoup(html, "html.parser")

            title = soup.find("title")
            cover_tag = soup.select_one("a.bigImage img")

            if not cover_tag:
                print(f"[{name}] 无封面,跳过")
                return

            cover_url = cover_tag["src"]
            if cover_url.startswith("/"):
                cover_url = host + cover_url

            ext = os.path.splitext(cover_url)[1]
            cover_path = os.path.join(SAVE_DIR, f"{name}{ext}")

            async with session.get(
                cover_url, 
                headers={"Referer": URL, **HEADERS}, 
                ssl=False
            ) as img_response:
                if img_response.status == 200:
                    with open(cover_path, "wb") as f:
                        f.write(await img_response.read())
                    
                    print(f"[{name}] 下载成功 → {cover_path}")
                    print(f"[{name}] 标题:{title.text.strip() if title else '无标题'}")
                else:
                    print(f"[{name}] 图片下载失败,状态码:{img_response.status}")

    except asyncio.TimeoutError:
        print(f"[{name}] 请求超时,跳过")
    except Exception as e:
        print(f"[{name}] 发生错误:{e}")
        return

async def start_to_download():
    names = [
        f"{SERIES}-{code:03d}" for code in range(max(1, START_CODE), END_CODE)
    ]
    
    connector = aiohttp.TCPConnector(limit=10)
    async with aiohttp.ClientSession(
        connector=connector,
        headers=HEADERS
    ) as session:

        tasks = [download_image_cover(session, name, SERIES) for name in names]
        
        batch_size = 10
        for i in range(0, len(tasks), batch_size):
            batch = tasks[i:i+batch_size]
            await asyncio.gather(*batch)
            await asyncio.sleep(0.5)  # 防止请求太快,被他们知道这是来自脚本的下载解析行为

if __name__ == "__main__":
    asyncio.run(start_to_download())

程序运行方式

shell
python ./series_image_cover_download.py
  • 上面的爬取方式,是针对一个大系列的番号进行爬取的。

  • 番号这个概念,就是某某厂商的一个系列,有点像买手机,比如三星手机,它会有外折叠机、内折叠机、直屏机等等,一一对应到Galaxy Z-Fold系列、Galaxy Z-Flip系列和Galaxy S系列。

  • 那么相应地在这里,黄色电影本身就是一个三星手机概念,而番号则是如同手机系列的概念,比如KBI什么的,而具体的片号,则是有番号 + 数字组成,比如 KBI-002 这种格式。

  • 那么针对想下载某一个片的所有详情图,就是解析样品图片这个区域的内容,那么具体的代码如下所示:

  • special_image_detail_config.py

python
videos = [
  {
    "name" : "KBI-001",
  },
  {
    "name": "KBI-002",
  }
]
  • special_image_detail_download.py
python
import os
import requests
from bs4 import BeautifulSoup

host = "https://www.javbus.com"

def image_download(names, onlyFolderImage = False):
    for name in names:
        URL = host + "/" + name
        SAVE_DIR = "./{}".format(name)

        HEADERS = {
            "authority": "www.javbus.com",
            "method": "GET",
            "path": f"/{name}",
            "scheme": "https",
            "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
            "accept-encoding": "gzip, deflate, br, zstd",
            "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
            "cookie": "PHPSESSID=nnelcojatbmsjh2s02i4m0fno4; existmag=mag; _tea_utm_cache_10000007=undefined",
            "dnt": "1",
            "priority": "u=0, i",
            "sec-ch-ua": '"Chromium";v="140", "Not=A?Brand";v="24", "Microsoft Edge";v="140"',
            "sec-ch-ua-mobile": "?0",
            "sec-ch-ua-platform": '"macOS"',
            "sec-fetch-dest": "document",
            "sec-fetch-mode": "navigate",
            "sec-fetch-site": "none",
            "sec-fetch-user": "?1",
            "upgrade-insecure-requests": "1",
            "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                        "AppleWebKit/537.36 (KHTML, like Gecko) "
                        "Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0"
        }

        os.makedirs(SAVE_DIR, exist_ok=True)

        session = requests.Session()
        response = session.get(URL, headers=HEADERS)
        soup = BeautifulSoup(response.text, "html.parser")

        title_tag = soup.find("title")
        title = title_tag.text.strip() if title_tag else "unknown_title"
        print("页面标题:", title)

        cover_tag = soup.select_one("a.bigImage img")
        if cover_tag:
            cover_url = cover_tag["src"]
            if cover_url.startswith("/"):
                cover_url = host + cover_url
            ext = os.path.splitext(cover_url)[1]
            cover_path = os.path.join(SAVE_DIR, f"cover{ext}")
            cover_path = cover_path.replace("cover.jpg", "folder.jpg")
            
            cover_headers = HEADERS.copy()
            cover_headers["referer"] = URL
            
            r = session.get(cover_url, headers=cover_headers, stream=True)
            with open(cover_path, "wb") as f:
                for chunk in r.iter_content(1024):
                    f.write(chunk)
            print("封面图已下载:", cover_path)

        if not onlyFolderImage:
            sample_tags = soup.select("a.sample-box")
            if not sample_tags:
                print("没有样本图")
            else:
                for idx, a_tag in enumerate(sample_tags, 1):
                    img_url = a_tag.get("href")
                    if not img_url:
                        continue
                    ext = os.path.splitext(img_url)[1]
                    img_path = os.path.join(SAVE_DIR, f"sample_{idx}{ext}")
                    r = session.get(img_url, headers=HEADERS, stream=True)
                    with open(img_path, "wb") as f:
                        for chunk in r.iter_content(1024):
                            f.write(chunk)
                    print(f"样本图 {idx} 已下载:", img_path) 

if __name__ == "__main__":
    from special_image_detail_config import videos
    
    image_download([video["name"] for video in videos])

程序运行方式

shell
python ./special_image_detail_download.py
  • 对于电影的下载部分
    • 一般来说,类似像JavBus这些地方,都会提供一些磁力链接,一般来说直接可以使用Motrix去进行下载,但是Motrix也是存在很大的问题,特别是下载这种不太合法的影片的时候,他的下载速度堪忧。
    • 两种解决方案:
      • 其一是采用:
        • 夸克网盘或者迅雷的会员版 去下载,虽然他们的云盘也是会屏蔽这一类的影片资源,但是你下载你的本地,这是没有任何问题的。
      • 第二种方式是:
        • 很多人会去Miss AV这种聚合平台观看这一类的影片,Miss AV的影片一般是采用M3U8的格式输出的,于是 ->
        • 我提供了另一个工具,可以下载这一类的M3U8的视频,详细可以观看这一篇关于M3U下载的文章

重要声明

  • 如果除我自己之外的人看到这篇文章,自己拿这段代码在内网玩一下就好了,不要去做什么造成更大影响面的法律风险事宜。
  • 这真的是会被抓的!

随便写写的,喜欢就好。