简单概述
本系列可能是一个比较长的系列,主要是对《Python3网络爬虫开发实战》前七章的一个内容总结并且熟悉使用一下相关的框架与技术。
任务目标
爬取电影数据网站https://ssr1.scrape.center/,此网站无反爬,数据通过服务端渲染,需要爬取的部分为列表页里面的电影数据详情。
任务目标解析
- 爬取https://ssr1.scrape.center/网站的列表页面,通过列表页面的内容获取到需要的URL
- 爬取https://ssr1.scrape.center/detail/{id}网站内的数据详情,需要获取的部分有:
- 电影标题
- 电影图片的url
- 电影上映时间
- 电影分类
- 电影评分
- 剧情简介
- 将内容存放到需要的数据库中
技术选型与爬取
如何爬取
本次爬取选择使用在urllib库的基础上诞生的requests库,此库对urllib库的部分功能进行了重新的封装和扩展,使得网页爬取变得更加的容易。
构建基础的爬取函数
这部分的爬取函数的构建同之前使用urllib库构建的基本类似,但requests库对其进行了一些扩展,可以更好的做一些请求操作。
def scrape_page(url):
logging.info('scraping url: %s', url)
# 进行异常操作
try:
response = requests.get(url=url)
if response.status_code == 200:
return response.text
logging.error('get invalid status code %s while scraping %s',
response.status_code, url)
except requests.RequestException:
logging.error('error occured while scraping %s', url, exc_info=True)
构建列表页的爬取函数
在基础的爬取函数上进行相关内容的爬取,因此这里只需要更改url后直接调用爬取函数即可。
# 爬取列表页
def scrape_index(page):
index_url = f'{BASE_URL}/page/{page}'
return scrape_page(index_url)
构建详情页的爬取函数
由于列表页解析后会获得所需要的url,因此我们直接调用url进行抓取页面即可。
# 爬取详情页
def scrape_detail(url):
return scrape_page(url)
如何解析
xpath规则解析
本次使用的方式不再是使用最基本的正则表达式进行解析,虽然正则表达式在构建后能够准确的找到我们所需要的内容,但是构建一个正则表达式还是比较繁琐的,因此这里使用了xpath规则进行网页节点的提取,可以利用xpath教程简单快速的学习使用xpath。
解析列表页
在之前使用re模块进行列表解析时,我们需要构建一个正则表达式,实际上利用正则表达式,其实是对获取的网页进行了字符串匹配,因此当出现多个节点层没有办法找到容易辨识的特征时,构建正则表达式其实会比较麻烦,而使用xpath规则时就直接变得很简单,很容易就能够获取到我们需要的内容:
# 解析列表页
def parse_index(html_str):
html = etree.HTML(html_str)
# 解析获取需要的内容
items = html.xpath('//a[@class="name"]/@href')
if not items:
return []
for item in items:
# 进行链接的拼接
detail_url = urljoin(BASE_URL, item)
# 输出生成的链接信息
logging.info('get detail url %s', detail_url)
# 生成器函数
yield detail_url
解析详情页
详情页的构建在之前需要多个正则表达式进行构建,而使用xpath规则时构建就容易许多,可以利用元素标签名+类名快速获取到我们所需要的内容,但re模块也不是没有特殊的用武之地,对于本次要抓取的电影网站来说,如果要获取到电影的上映时间,此时利用re模块获取速度能够特别的快。
# 解析详情页
def parse_detail(html_str, index):
html = etree.HTML(html_str)
# 电影标题
name = html.xpath('//h2[@class="m-b-sm"]/text()')[0] if html.xpath('//h2[@class="m-b-sm"]/text()') \
else None
# 电影图片URL
cover = html.xpath('//img[@class="cover"]/@src')[0] if html.xpath('//img[@class="cover"]/@src') \
else None
# 电影上映时间
published_at = re.compile('(\d{4}-\d{2}-\d{2}) 上映')
published = re.search(published_at, html_str).group(1) if re.search(published_at, html_str) \
else None
# 电影分类
categories = html.xpath('//button[contains(@class, "category")]/span/text()') \
if html.xpath('//button[contains(@class, "category")]/span/text()') \
else None
# 电影评分
score = html.xpath('//p[contains(@class, "score")]/text()')[0] \
if html.xpath('//p[contains(@class, "score")]/text()') \
else None
# 电影剧情简介
drama = html.xpath('//div[contains(@class, "drama")]/p/text()')[0] \
if html.xpath('//div[contains(@class, "drama")]/p/text()') \
else None
# 以字典形式返回所需要的内容
return {
'index': index,
'name': name,
'cover': cover,
'published': published,
'categories': categories,
'drama': drama.strip(),
'score': score.strip()
}
如何存储
本次存储使用了JSON格式的存储方式,由于我们获取数据时为字典,通过json模块能够十分便捷快速
的将数据存储,但是如果要在一个文件内存储,处理[]
就不算太容易了,因此我们可以在完成此任务后手动添加。
# 以json文件的方式进行存储
def save_data(data):
data_path = '{0}/movies.json'.format(RESULT_DIR)
# 创建一个进行内容的存放
with open(data_path, 'a+', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=2)
file.write(',')
file.write('\n')
附录-源代码
import json
import re
import requests
import logging
from os import makedirs
from os.path import exists
from lxml import etree
from urllib.parse import urljoin
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s')
# 常量
BASE_URL = 'https://ssr1.scrape.center'
TOTAL_PAGE = 10
RESULT_DIR = 'results'
exists(RESULT_DIR) or makedirs(RESULT_DIR)
# 网页内容的爬取
# 网页爬取
def scrape_page(url):
logging.info('scraping url: %s', url)
# 进行异常操作
try:
response = requests.get(url=url)
if response.status_code == 200:
return response.text
logging.error('get invalid status code %s while scraping %s',
response.status_code, url)
except requests.RequestException:
logging.error('error occured while scraping %s', url, exc_info=True)
# 爬取列表页
def scrape_index(page):
index_url = f'{BASE_URL}/page/{page}'
return scrape_page(index_url)
# 爬取详情页
def scrape_detail(url):
return scrape_page(url)
# 网页内容的解析
# 解析列表页
def parse_index(html_str):
html = etree.HTML(html_str)
# 解析获取需要的内容
items = html.xpath('//a[@class="name"]/@href')
if not items:
return []
for item in items:
# 进行链接的拼接
detail_url = urljoin(BASE_URL, item)
# 输出生成的链接信息
logging.info('get detail url %s', detail_url)
# 生成器函数
yield detail_url
# 解析详情页
def parse_detail(html_str, index):
html = etree.HTML(html_str)
# 电影标题
name = html.xpath('//h2[@class="m-b-sm"]/text()')[0] if html.xpath('//h2[@class="m-b-sm"]/text()') \
else None
# 电影图片URL
cover = html.xpath('//img[@class="cover"]/@src')[0] if html.xpath('//img[@class="cover"]/@src') \
else None
# 电影上映时间
published_at = re.compile('(\d{4}-\d{2}-\d{2}) 上映')
published = re.search(published_at, html_str).group(1) if re.search(published_at, html_str) \
else None
# 电影分类
categories = html.xpath('//button[contains(@class, "category")]/span/text()') \
if html.xpath('//button[contains(@class, "category")]/span/text()') \
else None
# 电影评分
score = html.xpath('//p[contains(@class, "score")]/text()')[0] \
if html.xpath('//p[contains(@class, "score")]/text()') \
else None
# 电影剧情简介
drama = html.xpath('//div[contains(@class, "drama")]/p/text()')[0] \
if html.xpath('//div[contains(@class, "drama")]/p/text()') \
else None
# 以字典形式返回所需要的内容
return {
'index': index,
'name': name,
'cover': cover,
'published': published,
'categories': categories,
'drama': drama.strip(),
'score': score.strip()
}
# 存储获取到的数据
# 以json文件的方式进行存储
def save_data(data):
data_path = '{0}/movies.json'.format(RESULT_DIR)
# 创建一个进行内容的存放
with open(data_path, 'a+', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=2)
file.write(',')
file.write('\n')
# 主函数
def main():
index = 0
for page_index in range(1, TOTAL_PAGE + 1):
# 获取列表页
page_html = scrape_index(page=page_index)
# 获取详情页
detail_urls = parse_index(page_html)
for detail_url in detail_urls:
index += 1
# 抓取详情页内容
detail_html = scrape_detail(detail_url)
# 获取数据
data = parse_detail(detail_html, index)
# 获取信息
logging.info('get detail data %s', data)
save_data(data=data)
if __name__ == '__main__':
main()
版权信息
本文由PorterZhang整理或写作完成
本人的Github: PorterZhang2021
本人的博客地址:PorterZhang