Selenium 爬虫实战

由于JavaScript动态渲染的页面不止Ajax一种,而且有时候即使是Ajax获取的数据,其Ajax接口中也包含有很多加密参数,使我们很难直接找出规律,所以为了解决这些问题,我们可以直接模拟浏览器的运行,然后爬取数据。

selenium的基础使用

from selenium import webdriver

browser = webdriver.Chrome()
browser.get('<https://www.taobao.com>')
print(browser.page_source)
browser.close()
   

在这段代码中,我们模拟一个chrome浏览器,浏览器会跳转到淘宝页面,这样就可以获取网页源代码,运行效果如下所示:

selenium爬取实战

爬取目标:Scrape | Movie

参考书籍:python3网络爬虫开发实战

加载列表页&详情页url

我们需要去观察每部电影的URL和Ajax请求,可以注意到电影详情页和之前是不一样的,之前的URL后面是https://spa1.scrape.center/detail/1,这样的有规律性的数字,而这次似乎是使用了base64编码而得的长字符串:Scrape | Movie

也就是说详情页的URL包含有加密参数,所以我们无法根据规律构造详情页的URL

依次点击列表前几页:

可以发现多了一个token参数,而且每次token都会更新,我们不知道token的生成逻辑,也就没法直接构造Ajax请求来爬取数据。

所以这里,我们用selenium绕过这个阶段,直接获取JavaScript最终渲染完成的页面源代码

任务目标:

  • 通过selenium遍历列表页,获取每部电影的详情页URL
  • 通过上一步获取的URL爬取每部电影的详情页
  • 从详情页中提取每部电影的名称、类别、分数、简介等

由于列表页的url变化还是有规律的,所以可以直接构造

'''
condition是页面加载成功的判断条件,locator是定位器,通过配置查询条件和参数来获取一个或多个节点
'''
def scrape_page(url, condition, locator):
    logging.info('scraping %s', url)
    try:
        browser.get(url)
        wait.until(condition(locator))
    except TimeoutException:
        logging.error('error occurred while scraping %s', url, exc_info=True)

# 爬取列表页
def scrape_index(page):
    url = URL.format(page=page)
    scrape_page(url, condition=EC.visibility_of_all_elements_located,
                locator=(By.CSS_SELECTOR, '#index .item'))

在加载列表页后,我们就可以从中提取出详情页的URL

def parse_index():
    elements = browser.find_elements_by_css_selector('#index .item .name')
    for element in elements:
        href = element.get_attribute('href')
        yield urljoin(URL, href)

爬取详情页信息

为了判断详情页是否加载成功,我们可以把条件设置为看是否电影名称已经加载出来:

def scrape_detail(url):
    scrape_page(url, condition=EC.visibility_of_element_located,
                locator=(By.TAG_NAME, 'h2'))

接下来逐个提取我们想要的信息就行:

def parse_detail():
    url = browser.current_url
    name = browser.find_element_by_tag_name('h2').text
    categories = [element.text for element in browser.find_elements_by_css_selector('.categories button span')]
    cover = browser.find_element_by_css_selector('.cover').get_attribute('src')
    score = browser.find_element_by_class_name('score').text
    drama = browser.find_element_by_css_selector('.drama p').text
    return {
        'url': url,
        'name': name,
        'categories': categories,
        'cover': cover,
        'score': score,
        'drama': drama
    }

设置无头模式

由于爬取过程中,弹出的浏览器可能有些干扰,这里可以设置无头模式:

options = webdriver.ChromeOptions()
options.add_argument('--headless')
browser = webdriver.Chrome(options=options)

完整代码:

from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import logging
from urllib.parse import urljoin
from os import makedirs
from os.path import exists
import json

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(levelname)s: %(message)s')

URL = '<https://spa2.scrape.center/page/{page}>'
TIMEOUT = 10
TOTAL_PAGE = 10
RESULTS_DIR = 'results'

exists(RESULTS_DIR) or makedirs(RESULTS_DIR)

options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)

options.add_argument('--headless')
browser = webdriver.Chrome(options=options)
wait = WebDriverWait(browser, TIMEOUT)

'''
condition是页面加载成功的判断条件,locator是定位器,通过配置查询条件和参数来获取一个或多个节点
'''
def scrape_page(url, condition, locator):
    logging.info('scraping %s', url)
    try:
        browser.get(url)
        wait.until(condition(locator))
    except TimeoutException:
        logging.error('error occurred while scraping %s', url, exc_info=True)

# 爬取列表页
def scrape_index(page):
    url = URL.format(page=page)
    scrape_page(url, condition=EC.visibility_of_all_elements_located,
                locator=(By.CSS_SELECTOR, '#index .item'))

def parse_index():
    elements = browser.find_elements_by_css_selector('#index .item .name')
    for element in elements:
        href = element.get_attribute('href')
        yield urljoin(URL, href)

def scrape_detail(url):
    scrape_page(url, condition=EC.visibility_of_element_located,
                locator=(By.TAG_NAME, 'h2'))

def parse_detail():
    url = browser.current_url
    name = browser.find_element_by_tag_name('h2').text
    categories = [element.text for element in browser.find_elements_by_css_selector('.categories button span')]
    cover = browser.find_element_by_css_selector('.cover').get_attribute('src')
    score = browser.find_element_by_class_name('score').text
    drama = browser.find_element_by_css_selector('.drama p').text
    return {
        'url': url,
        'name': name,
        'categories': categories,
        'cover': cover,
        'score': score,
        'drama': drama
    }

def save_data(data):
    name = data.get('name')
    data_path = f'{RESULTS_DIR}/{name}.json'
    json.dump(data, open(data_path, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)

def main():
    try:
        for page in range(1, TOTAL_PAGE + 1):
            scrape_index(page)
            detail_urls = parse_index()
            for detail_url in list(detail_urls):
                logging.info('get detail url %s', detail_url)
                scrape_detail(detail_url)
                detail_data = parse_detail()
                logging.info('detail data %s', detail_data)
                save_data(detail_data)
    finally:
        browser.close()

if __name__ == '__main__':
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默默无名的大学生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值