如何爬取系列博客内容到本地
2020-12-15 本文已影响0人
王灵
前言
案发经过:最近在学习python,对爬虫非常赶兴趣。在闲鱼上翻到有一本叫作《Python3网络爬虫开发实战》的书,但是又不想掏钱买,所以就在网上搜索pdf版。无意间找到了一篇博客。那么问题来了,我不想因为断网或者博客关闭什么的原因影响我,所以我要把它保存下来
思路
博客是这样的,里面有各个章节的目录,点击进入章节内容
20201215164204.jpg
我需要做什么:
- 保存目录页为本地html文件
- 爬取章节地址,并保存内容到本地html
- 替换目录html里的章节地址为本地地址
- 下载html里的img文件到本地
- 替换html里的网络图片为本地地址
实现
保存目录页为本地html文件
(本地文件名、html)
def get_path(url):
"""
获取本地的文件地址
新建文件夹 htmls 用于保存html文件
观察url地址https://cuiqingcai.com/5052.html
决定使用crawler_5052 来命名文件
:param url: 页面的网络地址
:return: 本地html的地址
"""
pattern = re.compile('https://cuiqingcai.com/(\d+).html')
for p in pattern.finditer(url):
return 'htmls/crawler_' + str(p.group(1)) + ".html"
通过bs4的BeautifulSoup获取soup对象,方便查找筛选我们需要的内容
import requests
from bs4 import BeautifulSoup
def request(url, encoding='utf-8'):
"""请求http返回soup对象"""
headers = {'Referer': url,
'User-Agent': """Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Mobile Safari/537.36""",
}
response = requests.get(url=url, headers=headers)
html = response.content.decode(encoding, "ignore")
return BeautifulSoup(html, 'html.parser')
筛选获取我们需要的内容valuable_content = soup.select('.post-body')[0]
由于是截取的html片段,但是我们需要在本地使用所以需要添加必要的html元素
def get_html(div):
"""获取能够展示的html
soup.select('.post-body')
"""
return """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
%s
</body>
</html>""" % (div)
本地地址和html文本都获取了,接下来就是写入了
def write_file(file_name, str):
"""
把html文本写入本地文件
:param file_name: 文件路径
:param str: html文本
:return:
"""
# 打开文件
fo = open(file_name, "w+")
print("文件名: ", fo.name)
fo.write(str)
# 关闭文件
fo.close()
通过save_page
把它们串连起来
def save_page(url):
"""
保存页面到本地
:param url: 网页地址
:return:
"""
soup = request(url)
# 获取真正需要到内容
valuable_content = soup.select('.post-body')[0]
html = get_html(valuable_content)
# 生成一个文件名 crawler
name = get_path(url)
write_file(name, html)
爬取章节地址,并保存内容到本地html
读取刚刚保存的目录html文件,从里面筛选出章节地址
def save_pages():
"""
保存章节内容
:return:
"""
# 获取本地html文件的soup对象
soup = BeautifulSoup(open('htmls/crawler_5052.html', 'r', encoding='utf-8'), 'html.parser')
for i in soup.select('ul li a'):# 筛选出章节的地址
# 内容有一个错误,
child = i.attrs['href'].replace('hhttps', 'https')
# 保存url到本地
save_page(child)
替换目录html里的章节地址为本地地址
def replace_paths():
"""
替换目录html里的章节地址为本地文件
:return:
"""
# 获取本地html文件的soup对象
soup = BeautifulSoup(open('htmls/crawler_5052.html', 'r', encoding='utf-8'), 'html.parser')
html = str(soup)
for i in soup.select('ul li a'): # 筛选出章节的地址
# 内容有一个错误,
child = i.attrs['href'].replace('hhttps', 'https')
# 获取本地路径 需要点击打开,所以要使用相对路径
path = os.path.join('..', get_path(child))
# 替换
html = html.replace(child, path)
# 重新写入
write_file('htmls/crawler_5052.html', html)
下载html里的img文件到本地
这里只实现下载单个图片
获取图片的本地路径
def get_img_path(url):
"""
根据图片地址获取本地名称
:param url:
:return:
"""
# 有一些筛选出来的img里不是网络图片
if not (str(url).startswith('https') | str(url).startswith('http')):
return None
# 获取host后面的字段
pattern = re.compile('[.*][com/|.cn/](.*)')
for p in pattern.finditer(url):
f = p.group(1)
if f.endswith('.png') | f.endswith('.jpg') | f.endswith('.ico'):
f = f.replace('/', '_').replace('-', '_')
else: # 'http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=1708002eb50fe93aef25'
f = f.split('key=')[1] + '.jpg'
return os.path.join('image', f)
def download(url, path=None):
"""
下载图片到本地
:param url:图片网络地址
:param path:图片本地地址
:return:
"""
if not path:
path = get_img_path(url)
if not path:
return
# 判断这个文件是否存在了
try:
os.path.exists(path)
except:
print("")
if os.path.exists(path):
return
# filepath = os.path.join(url, path)
# urlretrieve(url, path)
print('path: ' + path)
file_data = requests.get(url, allow_redirects=True).content
with open(path, 'wb') as handler:
handler.write(file_data)
替换html里的网络图片为本地地址
def replace_imgs():
"""
查找文件中的图片 下载 替换
:return:
"""
# 获取文件夹下所有的文件
for f in os.listdir('htmls'):
replace_img(os.path.join('htmls', f))
def replace_img(f):
"""
:param f:
:return:
"""
soup = BeautifulSoup(open(f, 'r+', encoding='utf-8'), 'html.parser')
html = str(soup)
for i in soup.select('img'):
image_url = i.attrs['src']
image_name = get_img_path(image_url)
# 获取不到本地名称就不处理
if image_name:
# 下载到本地
download(image_url, image_name)
# 替换 ../image/wp_content_uploads_2019_10_865x90.png
# print(os.path.join('../image', os.path.basename(image_name)))
html = html.replace(image_url, os.path.join('../image', os.path.basename(image_name)))
# 重新写入
write_file(f, html)
整个文件
from utils import request
from bs4 import BeautifulSoup
import re
import os
import requests
# 先获取一页的内容看看
url = "https://cuiqingcai.com/5052.html"
def get_html(div):
"""获取能够展示的html
soup.select('.post-body')
"""
return """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
%s
</body>
</html>""" % (div)
def get_path(url):
"""
获取本地的文件地址
新建文件夹 htmls 用于保存html文件
观察url地址https://cuiqingcai.com/5052.html
决定使用crawler_5052 来命名文件
:param url: 页面的网络地址
:return: 本地html的地址
"""
pattern = re.compile('https://cuiqingcai.com/(\d+).html')
for p in pattern.finditer(url):
return 'htmls/crawler_' + str(p.group(1)) + ".html"
def write_file(file_name, str):
"""
把html文本写入本地文件
:param file_name: 文件路径
:param str: html文本
:return:
"""
# 打开文件
fo = open(file_name, "w+")
print("文件名: ", fo.name)
fo.write(str)
# 关闭文件
fo.close()
def save_page(url):
"""
保存页面到本地
:param url: 网页地址
:return:
"""
soup = request(url)
# 获取真正需要到内容
valuable_content = soup.select('.post-body')[0]
html = get_html(valuable_content)
# 生成一个文件名 crawler
name = get_path(url)
write_file(name, html)
def save_pages():
"""
保存章节内容
:return:
"""
# 获取本地html文件的soup对象
soup = BeautifulSoup(open('htmls/crawler_5052.html', 'r', encoding='utf-8'), 'html.parser')
for i in soup.select('ul li a'): # 筛选出章节的地址
# 内容有一个错误,
child = i.attrs['href'].replace('hhttps', 'https')
# 保存url到本地
save_page(child)
def replace_img(f):
"""
:param f:
:return:
"""
soup = BeautifulSoup(open(f, 'r+', encoding='utf-8'), 'html.parser')
html = str(soup)
for i in soup.select('img'):
image_url = i.attrs['src']
image_name = get_img_path(image_url)
# 获取不到本地名称就不处理
if image_name:
# 下载到本地
download(image_url, image_name)
# 替换 ../image/wp_content_uploads_2019_10_865x90.png
# print(os.path.join('../image', os.path.basename(image_name)))
html = html.replace(image_url, os.path.join('../image', os.path.basename(image_name)))
# 重新写入
write_file(f, html)
def download(url, path=None):
"""
下载图片到本地
:param url:图片网络地址
:param path:图片本地地址
:return:
"""
if not path:
path = get_img_path(url)
if not path:
return
# 判断这个文件是否存在了
try:
os.path.exists(path)
except:
print("")
if os.path.exists(path):
return
# filepath = os.path.join(url, path)
# urlretrieve(url, path)
print('path: ' + path)
file_data = requests.get(url, allow_redirects=True).content
with open(path, 'wb') as handler:
handler.write(file_data)
def get_img_path(url):
"""
根据图片地址获取本地名称
:param url:
:return:
"""
# 有一些筛选出来的img里不是网络图片
if not (str(url).startswith('https') | str(url).startswith('http')):
return None
# 获取host后面的字段
pattern = re.compile('[.*][com/|.cn/](.*)')
for p in pattern.finditer(url):
f = p.group(1)
if f.endswith('.png') | f.endswith('.jpg') | f.endswith('.ico'):
f = f.replace('/', '_').replace('-', '_')
else: # 'http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=1708002eb50fe93aef25'
f = f.split('key=')[1] + '.jpg'
return os.path.join('image', f)
def replace_imgs():
"""
查找文件中的图片 下载 替换
:return:
"""
# 获取文件夹下所有的文件
for f in os.listdir('htmls'):
replace_img(os.path.join('htmls', f))
def replace_paths():
"""
替换目录html里的章节地址为本地文件
:return:
"""
# 获取本地html文件的soup对象
soup = BeautifulSoup(open('htmls/crawler_5052.html', 'r', encoding='utf-8'), 'html.parser')
html = str(soup)
for i in soup.select('ul li a'): # 筛选出章节的地址
# 内容有一个错误,
child = i.attrs['href'].replace('hhttps', 'https')
# 获取本地路径 需要点击打开,所以要使用相对路径
path = os.path.join('..', get_path(child))
# 替换
html = html.replace(child, path)
# 重新写入
write_file('htmls/crawler_5052.html', html)
if __name__ == '__main__':
# save_page(url)
# save_pages()
# replace_paths()
replace_imgs()