写了一个下载图片和视频的python小工具

发布时间 2023-06-10 20:20:00作者: 琴水玉

谁先掌握了 AI, 谁就掌握了未来的“权杖”。


在网上冲浪时,总会遇到一些好看的视频和图片,情不自禁地想“据为己有”。于是,就有了下载图片和视频的需求。

浏览器插件

工欲善其事,必先利其器。要做一件事,首先得找到对应的工具。下载网络图片或视频,当然首选浏览器插件了。

使用 Microsoft Edge Dev 版,安装如下插件即可(安装插件的方法网上搜下即可):


Python 小工具

有了浏览器插件,为什么我还要写这个小工具呢?

我的需求场景是,有一个入口页面(如下图所示),这个页面有一系列子页面和链接,每个子页面有一个视频。我想批量下载这些子页面的视频。使用浏览器插件,我得一个个点击子页面,子页面比较多时,是个繁琐的事情。

能不能用程序来实现呢?说干就干。直接上程序。

思路说明:

(1) 使用 selenium 来模拟打开页面,因为有些页面需要完全打开,否则内容会获取不到或者获取不完整;

(2) 使用 CSS 来定位网页链接元素和资源元素;

(3) CSS 元素可以通过命令行指定,使用更加方便。

用法和例子都有。要使用这个工具,需要:

(1)安装如下 python 模块:

pip3 install selenium requests beautifulsoup4 Pillow

(2) 下载对应的 chromedriver

# download corresponding version of chromedriver in https://chromedriver.chromium.org/downloads
# unzip and cp chromedriver to /usr/local/bin/  then chmod +x /usr/local/bin/chromedriver

程序实现:

#!/usr/bin/python3
#_*_encoding:utf-8_*_

import os
import random
import string
import json
import time
import argparse
import traceback
import subprocess


from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

import requests
from bs4 import BeautifulSoup
from PIL import Image


# 下载目录设置
save_path = '/Users/qinshu/Downloads'
img_width_threshold = 500
img_height_threshold = 500

def usage():

    usage_info = '''
        This program is used to batch download pictures or videos from specified url.
        search and download pictures or videos from network url by specified rules.

        options:
              -u --url  one html url is required
              -c --classname elements specified by class name of html elements
              -s --css elements specified by css selector
              -a --attr elements specified by custom attribute of html elements
              -i --include filter keywords to specify which link to retain
              -t --type specify source type eg. img, video

        eg.
             python3 dw.py -u https://dp.pconline.com.cn/list/all_t601.html -i photo
             python3 dw.py -u https://dp.pconline.com.cn/list/all_t601.html -c picLink
             python3 dw.py -u https://dp.pconline.com.cn/list/all_t601.html -c picLink -t img
             python3 dw.py -u https://dp.pconline.com.cn/list/all_t601.html -c picLink -t img -s "#J-BigPic img"
             python3 dw.py -u https://dp.pconline.com.cn/photo/5197753.html -s ".picTxtList .lWork"
             python3 dw.py -u https://space.bilibili.com/1995535864 -c cover  -t video
             python3 dw.py -u 'https://search.bilibili.com/all?vt=63933273&keyword=%E8%88%9E%E8%B9%88&from_source=webtop_search&spm_id_from=333.1007&search_source=5' -a 'data-mod=search-card' -i video -t video

    '''

    print(usage_info)


def parseArgs():
    try:
        description = '''This program is used to batch download pictures or videos from specified urls.
                                will search and download pictures or videos from network url by specified rules.
                      '''
        parser = argparse.ArgumentParser(description=description)
        parser.add_argument('-u','--url', help='one html url is required', required=True)
        parser.add_argument('-c','--classname', help='a href link elements specified by class name', required=False)
        parser.add_argument('-s','--css', help='a href link elements specified by css selector', required=False)
        parser.add_argument('-a','--attr', help='a href link elements specified by custom attribute selector', required=False)
        parser.add_argument('-i','--include', help='a href link elements filtered by keywords to specify which link to retain', required=False)
        parser.add_argument('-t','--type', help='source type specified, eg. img, video', required=False)
        args = parser.parse_args()
        print(args)
        url = args.url
        clsname = args.classname
        css = args.css
        attr = args.attr
        include = args.include
        sourcetype = args.type
        print("%s %s %s %s %s %s" % (url, clsname, css, attr, include, sourcetype))
        return (url, clsname, css, attr, include, sourcetype)
    except:
        usage()
        traceback.print_exc()
        exit(1)

# 生成随机字符串
def generate_random_string(length):
    # 生成随机数字和字母组合的序列
    rand_seq = string.ascii_letters + string.digits
    return ''.join(random.choice(rand_seq) for _ in range(length))


def picsubffix():
    return ["png", "jpg", "jpeg", "tif", "webp"]


def end_with_pic_suffix(link):
    for s in picsubffix():
        if link.endswith(s):
            return True
    return False

def get_suffix(link):
    return link.rsplit(".", 1)[-1]

def complete(link):
    if link.startswith("//"):
        return "https:" + link
    return link

def build_soup(url):

    options = webdriver.ChromeOptions()
    options.add_argument('User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3')

    with webdriver.Chrome(options=options) as driver:
        driver.get(url)

        try:
            # 将页面滚动到底部,确保所有内容都被加载和渲染
            driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
        except:
            print("not scroll to window bottom")

        driver.implicitly_wait(30)

        # 获取完整页面内容
        full_page_content = driver.page_source
        #print(full_page_content)

        # 关闭浏览器

        driver.quit()
        return BeautifulSoup(full_page_content, 'html.parser')

def build_soup_use_request(url):

    response = requests.get(url)
    #print(response.text)
    return BeautifulSoup(response.text, 'html.parser')


def getlinks(url, clsname, css_selectors, attr, filter_func=None):

    all_links = []
    if clsname:
        all_links.extend(getlinks_by_class(url, clsname, filter_func))

    if css_selectors:
        all_links.extend(getlinks_by_css(url, css_selectors, filter_func))

    if attr:
        all_links.extend(getlinks_by_attr(url, attr, filter_func))

    if len(all_links) != 0:
        return all_links

    return getlinks_all(url, filter_func)


def get_links_from_elements(elements, filter_func=None):
    links = set()
    for element in elements:
        link = element.get('href')
        if link:
            if not filter_func:
                links.add(complete(link))
                #print(link)
            else:
                if filter_func(link):
                    #print("filtered:%s" % link)
                    links.add(complete(link))

    return links

def getlinks_by_css(url, css_selectors, filter_func=None):

    soup = build_soup(url)
    elements = soup.select(css_selectors)
    return get_links_from_elements(elements, filter_func)

def getlinks_by_attr(url, attr, filter_func=None):

    soup = build_soup(url)
    attr_parts = attr.split("=")
    key = attr_parts[0]
    value = attr_parts[1]

    elements = soup.find_all('a', attrs={ key: value })
    return get_links_from_elements(elements, filter_func)

def getlinks_all(url, filter_func):

    soup = build_soup(url)
    elements = soup.find_all('a')
    return get_links_from_elements(elements, filter_func)

def getlinks_by_id_class(url, pid, clsname, filter_func=None):

    soup = build_soup(url)
    my_div = soup.find(id=pid)
    elements = my_div.find_all('a', class_=clsname)
    return get_links_from_elements(elements, filter_func)

def getlinks_by_class(url, clsname, filter_func=None):

    all_elements = []
    soup = build_soup(url)

    # a.className = clsname
    all_elements.extend(soup.find_all('a', {'class': clsname}))

    div_elements = soup.find_all("div", {'class': clsname})
    for div_e in div_elements:
        a_elements = soup.find_all("a")
        all_elements.extend(a_elements)

    return get_links_from_elements(all_elements, filter_func)

def download_source(url, sourcetype, css):

    print("url %s sourcetype %s" % (url, sourcetype))

    # use you-get to download bilibili videoes
    if "bilibili.com" in url and sourcetype == "video":
        download_bibi(url, sourcetype)
        return

    # common download
    if css:
        soup = build_soup(url)
        elements = soup.select(css)
    else:
        soup = build_soup(url)
        elements = soup.find_all(sourcetype)

    download_from_elements(elements)

def download_from_elements(elements):
    for e in elements:
        src = e.get("src")
        if src:
            c_src = complete(src)

            if sourcetype == 'img' and end_with_pic_suffix(src):
                response = requests.get(c_src)
                delete_flag = False

                pic_filename = save_path + "/" + generate_random_string(16)+ "." + get_suffix(src)
                with open(pic_filename, "wb") as file:
                    file.write(response.content)
                with Image.open(pic_filename) as img:
                    width, height = img.size
                    #print("width: %s height:%s " % (width,height))
                    if width < img_width_threshold or height < img_height_threshold:
                        delete_flag = True
                if delete_flag:
                    os.remove(pic_filename)

            if sourcetype == 'video':
               cmd = "you-get " + c_src
               os.system(cmd)
               print('downloaded successfully! src: %s' % src)


def download_bibi(url, sourcetype):
    if sourcetype == 'video':
        cmd = "you-get " + url
        os.system(cmd)


if __name__ == "__main__":

    (url, clsname, css, attr, include, sourcetype) = parseArgs()

    filter_func = None
    if include:
        filter_func = lambda x : include in x
    links = getlinks(url, clsname, css, attr, filter_func)
    print("links:")
    print(links)
    print("number of links: %s"  % len(links))

    if len(links) > 0:
        for link in links:
            if sourcetype:
                time.sleep(1)
                download_source(link, sourcetype, css)





与 ChatAI 高效协作

这个程序的主要部分是 ChatAI 编写的。我只是做了点程序的润色工作。

如何与 ChatAI 高效协作呢?一种简单的方式是,你问我答。但是,简单的提问,往往得不到理想的回答。读者不妨试试以下几种提问,看看 ChatAI 给出什么答案:

  1. 写一个 python 程序,下载页面 https://tu.heiguang.com/works/12_森系.html 中 class=tk_photo_img 的链接列表;

  2. 能够使用 -c 来指定 classname;

  3. 使用模块化的方式来编写这个程序;

  4. 使用 -k --keyword 来指定一个关键字,链接包含该关键字才输出;

  5. 写一个模块化的 python 程序,获取页面 https://tu.heiguang.com/works/12_森系.html 中 class=tk_photo_img 中的链接列表;可以使用 -c 来指定 classname, 使用 -k 来指定一个关键字,链接包含该关键字才输出;如果指定了 -t type ,则下载指定类型的资源。type 可以取 img 或 video;

  6. 写一个模块化的 python 程序,获取页面 https://tu.heiguang.com/works/12_森系.html 中 class=tk_photo_img 中的链接列表;可以使用 -c 来指定 classname, 使用 -k 来指定一个关键字,链接包含该关键字才输出;如果指定了 -t type ,则下载指定类型的资源。type 可以取 img 或 video,要求使用 selenium 来模拟打开页面并写成函数,获取链接也写成一个单独的函数,下载资源也写成单独的函数。

  7. 写一个模块化的 python 程序,获取页面 https://tu.heiguang.com/works/12_森系.html 中 class=tk_photo_img 中的链接列表。要求如下:

(1) 可以使用 -c 来指定 classname;
(2) 可以使用 -s 来指定 css 选择符;
(3) 可以使用 -a 来指定自定义属性;
(4) -c -s -a 可以自由组合;
(5) 使用 -k 来指定一个关键字,链接包含该关键字才输出;
(6) 如果指定了 -t type ,则下载指定类型的资源。type 可以取 img 或 video;
(7) 要求使用 selenium 来模拟打开页面并写成函数,获取链接也写成一个单独的函数,下载资源也写成单独的函数。

请直接给出完整程序。是不是体会到了出题者的乐趣?

AI 给出的程序如下:

import argparse
import os
import re
import requests
from bs4 import BeautifulSoup
from selenium import webdriver

def open_browser(url):
    '''使用 selenium 打开指定页面,并返回 driver 对象'''
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')
    options.add_argument('--disable-gpu')
    options.add_argument('--no-sandbox')
    browser = webdriver.Chrome(options=options)
    browser.get(url)
    return browser

def extract_image_links(html, args):
    '''从 HTML 中提取图片链接'''
    soup = BeautifulSoup(html, 'html.parser')
    if args.css_selector:
        elements = soup.select(args.css_selector)
    elif args.classname:
        elements = soup.find_all(class_=args.classname)
    else:
        elements = soup.find_all('img')
    link_regex = re.compile(r'https?://[\w/\-?=%.]+\.(jpg|jpeg|gif|png)')
    image_links = []
    for element in elements:
        if args.attr:
            attr_value = element.get(args.attr)
            if attr_value and (not args.keyword or args.keyword in attr_value):
                match = link_regex.search(attr_value)
                if match:
                    image_links.append(match.group())
        else:
            if element.has_attr('src') and (not args.keyword or args.keyword in element['src']):
                match = link_regex.search(element['src'])
                if match:
                    image_links.append(match.group())
    return image_links

def download_media(url, media_type, output_dir):
    '''下载指定类型的媒体文件'''
    response = requests.get(url, stream=True)
    filename = os.path.basename(url)
    output_path = os.path.join(output_dir, filename)
    with open(output_path, 'wb') as f:
        for chunk in response.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)
    print(f'{media_type} saved to: {output_path}')

def main():
    parser = argparse.ArgumentParser(description='Download images from a website.')
    parser.add_argument('url', type=str, help='URL of the website to download images from')
    parser.add_argument('--classname', '-c', type=str, help='class name of the HTML element to extract links from')
    parser.add_argument('--css-selector', '-s', type=str, help='CSS selector of the HTML element to extract links from')
    parser.add_argument('--attr', '-a', type=str, help='attribute name of the HTML element to extract links from')
    parser.add_argument('--keyword', '-k', type=str, help='only download images that contain this keyword')
    parser.add_argument('--type', '-t', type=str, choices=['img', 'video'], help='type of media to download')
    parser.add_argument('--output', '-o', type=str, default='.', help='output directory')
    args = parser.parse_args()

    browser = open_browser(args.url)
    html = browser.page_source
    browser.quit()
    image_links = extract_image_links(html, args)

    if not os.path.exists(args.output):
        os.mkdir(args.output)

    for link in image_links:
        if args.type:
            if args.type == 'img' and link.endswith(('jpg', 'jpeg', 'gif', 'png')):
                download_media(link, 'Image', args.output)
            elif args.type == 'video' and link.endswith(('mp4', 'avi', 'wmv', 'flv')):
                download_media(link, 'Video', args.output)
        else:
            download_media(link, 'Image', args.output)

if __name__ == '__main__':
    main()

虽然 AI 写出的程序还需要“调教”一下,但是,—— 我写出程序花费了 4 个小时,而 AI 只花费了不到一分钟。要知道,我是有着十年开发经验的工程师啊!就算 python 不太熟练,这个倍数也不会少多少。

我似乎看到了程序员面临的一次可怕的危机和可贵的机遇。可怕的危机我就不说了。可贵的机遇在于,熟练的开发者,完全可能单人单周从零构建起一个应用。也就是说,创业的技术基础已经形成,程序员需要更多学习商业、运营、财务、心理学相关的知识和技能。

​对于初入行业的程序员,我的忠告是:尽可能早地掌握使用 AI 来协作。如果能善用 AI 的力量,他的成长速度和工作效率可能是其他人的数倍之多。

这篇文章,也可算是我和 ChatAI 的共同作品吧!

结语

这个程序虽微小,它承载着一个伟大的目标:下载互联网上的任意指定资源。前进吧,少年!

相似文章