批量爬取 pexels 图片

  • 【批量爬取 pexels 图片】闲来无事尝试写了个爬虫爬取 pexels 上的图片内容,遇到了一些问题来记录下
    主要问题
  • 网站反爬, 借助 selenium 绕过
  • 网站对 selenium 也做了反爬处理,识别为 webdriver 时,js 文件获取 403,想办法隐藏 webdriver 身份绕够反爬
  • selenium 无法在页面上采集到想要的链接(准确应该说是可以采集到小图的链接,但是小图的分辨率不够),研究下图片规律,发现每个图片有自己的 id 获取图片 id 自己拼接 url 下载
  • 拼接 url 不知道如何提升分辨率,好在 pexels 提供了默认的下载方式,是一个 download 链接,使用该链接可以下载图片,需要注意的是该链接会重定向到新的图片 url 所以不能直接用 download 链接下载,而是用其重定向的链接下载内容
源码
import requests import time import os import loggingfrom urllib.parse import urlparse from selenium import webdriver from multiprocessing import PoolPEXELS_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_idsdef 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 Noneresponse = requests.get(image_url, headers) if response.status_code != 200: message = '下载 {} 失败. status_code: {}'.format(image_url, response.status_code) logging.error(message) return Noneprefix = 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 locationdef 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地址

    推荐阅读