• 闲来无事尝试写了个爬虫爬取 pexels 上的图片内容,遇到了一些问题来记录下

    主要问题

  • 网站反爬, 借助 selenium 绕过
  • 网站对 selenium 也做了反爬处理,识别为 webdriver 时,js 文件获取 403,想办法隐藏 webdriver 身份绕够反爬
  • selenium 无法在页面上采集到想要的链接(准确应该说是可以采集到小图的链接,但是小图的分辨率不够),研究下图片规律,发现每个图片有自己的 id 获取图片 id 自己拼接 url 下载
  • 拼接 url 不知道如何提升分辨率,好在 pexels 提供了默认的下载方式,是一个 download 链接,使用该链接可以下载图片,需要注意的是该链接会重定向到新的图片 url 所以不能直接用 download 链接下载,而是用其重定向的链接下载内容

源码

import requests
import time
import os
import logging

from urllib.parse import urlparse
from selenium import webdriver
from multiprocessing import Pool


PEXELS_URL = 'https://www.pexels.com/'
DOWNLOAD_URL_KEY = 'https://www.pexels.com/photo/{image_id}/download/'
headers = {
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'
}
DOWNLOAD_LENGTH = 50  # 设置图片下载数, 这个是页面元素最少的数, 实际下载数大于这个数
SCROLL_HEIGHT = 2000  # 滚屏像素点
SLEEP_SECONDS = 5  # 睡眠秒数
CPU_COUNT = os.cpu_count()
logging.basicConfig(
    filename='log.txt',
    level=logging.INFO,
    filemode='w+',
    format='%(levelname)s:%(asctime)s: %(message)s',
    datefmt='%Y-%d-%m %H:%M:%S'
)
IMAGE_PATH = './images/'
EXISTED_IMAGES = set(os.listdir(IMAGE_PATH))


def get_image_ids():
    """
    通过 selenium 获取网站中的图片 ids
    """
    browser = webdriver.Chrome(executable_path='./chromedriver')
    # 隐藏 window.navigator.webdriver 避免反爬处理
    # 废了好大劲在这个文章找到答案 https://juejin.cn/post/6844904095749242887 感谢作者
    browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": """
        Object.defineProperty(navigator, 'webdriver', {
          get: () => undefined
        })
      """
    })

    url = PEXELS_URL
    browser.get(url)
    browser.maximize_window()
    elements = browser.find_elements_by_xpath('//article')
    scroll_height = SCROLL_HEIGHT
    while len(elements) < DOWNLOAD_LENGTH:
        browser.execute_script('window.scrollTo(0, {})'.format(scroll_height))  # 利用 selenium 执行 js 滚动到页面底部
        time.sleep(SLEEP_SECONDS)
        scroll_height += SCROLL_HEIGHT
        elements = browser.find_elements_by_xpath('//article')
    image_ids = [ele.get_attribute('data-photo-modal-medium-id') for ele in elements]
    browser.close()
    logging.info(f'image_ids: {image_ids}')
    return image_ids


def get_download_urls(image_ids):
    return [DOWNLOAD_URL_KEY.format(image_id=_id) for _id in image_ids]


def download_image(image_url):
    parse_result = urlparse(image_url)
    path = parse_result.path
    image_name = path.split('/')[-1]
    if image_name in EXISTED_IMAGES:
        logging.info(f'图片 {image_name} 已存在无需重新下载')
        return None

    response = requests.get(image_url, headers)
    if response.status_code != 200:
        message = '下载 {} 失败. status_code: {}'.format(image_url, response.status_code)
        logging.error(message)
        return None

    prefix = IMAGE_PATH
    with open(prefix + image_name, 'wb') as image:
        image.write(response.content)
    message = '下载 {} 成功. url: {}'.format(image_name, image_url)
    logging.info(message)


def get_image_url(need_redirect_url):
    """
    因为没法解决反爬, 这里采取其他方式绕过反爬
    1. 利用 selenium 获取到页面上 download 按钮的 url
    2. 这个地方 download 按钮的 url 并不能拿到图片的 url, 经过测试发现进行了重定向然后重定向的 url 才是图片 url
    3. 这个 download 按钮的 url 也有反爬, 测试发现 get 请求绕不过
    4. 但是测试发现可以用 head 请求获取到重定向的图片 url
    5. http code 302 返回的 response headers 里面的 location 即为重定向的 url
    """
    response = requests.head(need_redirect_url, headers=headers)
    if response.status_code != 302:
        message = '{} 没有发生重定向. code: {}'.format(need_redirect_url, response.status_code)
        logging.error(message)
        return None
    location = response.headers.get('location')
    logging.info(f'get_image_url success. location: {location}')
    return location


def download(need_redirect_url):
    image_url = get_image_url(need_redirect_url)
    if image_url:
        download_image(image_url)


def main():
    image_ids = get_image_ids()
    download_urls = get_download_urls(image_ids)
    logging.info(f'image_ids: {image_ids}, download_urls: {download_urls}')

    p = Pool(CPU_COUNT // 2)
    for url in download_urls:
        p.apply_async(func=download, args=(url,))

    p.close()
    p.join()


if __name__ == "__main__":
    main()

注意事项

  • 如果要用这个爬虫,需要去下载浏览器相应的 webdriver,需要注意的是浏览器的版本号,我这里是 chrome 版本 92.0.4515.107
  • 当前版本的 chrome 隐藏 webdriver 身份的方式如我文章所写,其他版本不知道有没有变动,所以程序可不可以生效就不知道了
  • 本人亲测有效
  • 后来发现 pexels 还对外提供了 api 不过有使用频率限制,不行写爬虫又需要用图片的可以去研究一下他的 api地址

pi12138
0 声望0 粉丝