最近闲着没事,学习一下PyQt的操作,偶然间看到CSDN的一位大神的博客(seniorwizard的博客-CSDN博客)有一系列的教程,公司内网连不上互联网,于是想着把所有的文章都爬下来,导到内网中慢慢看,做了个笔记吧.
爬虫配置
以前写过爬虫,用的是urllib,有点原始,这次想偷个懒,在scrapy框架下爬取点东西.开发机里默认安装了anaconda,安装scrapy的过程就显得很简单了
pip install scrapy
按照scrapy项目的基本构建,搭建项目csdn,生成爬虫csdn_spider,域名范围为blog.csdn.net
scrapy startproject csdn
cd csdn
scrapy genspider csdn_spider blog.csdn.net
爬虫需要递归爬取两层目录,包括目录页以及内容页,爬取任务比较简单,爬虫的设置需要打开管道文件的配置,由于日志跑的太多,设置了一下日志级别,又简单做了一下设置,在setting.py中
ITEM_PIPELINES = {
'csdn.pipelines.CsdnPipeline': 300,
}
LOG_LEVEL = "WARNING"
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent':'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36'
}
网页爬取
准备工作做的差不多了,开始写爬虫了.在准备工作中我们生成了一个爬虫器csdn_spider,在scrapy生成的项目中,爬虫器的文件在spiders/csdn_spider.py,我们爬取两层页面,计划按照深度优先的方式爬取,在目录页爬取到一篇文章的链接后,继续爬取这篇文章的内容,再返回目录页爬取下一篇文章的链接. 首先,定义我们爬取的目录页的元素,在items.py中,我们生成一个CsdnItem的类,用于爬取目录页的元素,包括文章标题,文章链接,文章描述及发表日期(虽然没啥用,权当顺手练习).
class CsdnItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
description = scrapy.Field()
post_date = scrapy.Field()
在爬虫器中,重载start_requests方法,逐步爬取目录页.通过对目录页的观察,目录页的链接的变化只在于其页面的变化,因此采用固定的规则变换页数即可,同时回调parse函数,以处理目录页的数据
url = 'https://blog.csdn.net/seniorwizard/category_1653109{}.html'
def start_requests(self):
for i in range(1, 5):
if i == 1:
url = self.url
url = self.url.format("_" + str(i))
yield scrapy.Request(
url=url,
callback=self.parse
)
在parse函数中,主要是分析文章标题以及文章链接的构成,相关元素的提取使用xpath来分析
def parse(self, response):
items = CsdnItem()
article_list = response.xpath("//ul[@class='column_article_list']/li")
for article_info in article_list:
items['link'] = article_info.xpath("./a/@href").get()
items['title'] = article_info.xpath("./a/div[@class='column_article_title']/h2/text()").get().strip()
items['description'] = article_info.xpath("./a/div[@class='column_article_desc']/text()").get().strip()
items['post_date'] = article_info.xpath(
"./a/div[@class='column_article_data']/span[2]/text()").get().strip()
yield scrapy.Request(
url=items['link'],
callback=self.parse2
)
处理完目录页后,进入文章内容页的爬取.找了很多办法,想把整个页面都download下来,通过编码的方式实现起来比较麻烦,后面参考了edge插件circle阅读助手的方式,将页面的样式等剔除掉,只保留内容数据.构建items.py中的文章内容页元素
class ArticleItem(scrapy.Item):
header = scrapy.Field()
title = scrapy.Field()
body = scrapy.Field()
在这个元素中,只爬取header,title和body,header其实后面没用到^-^,主要还是title和body,在爬虫器csdn_spider.py中,增加对文章内容的爬取
def parse2(self, response):
article_item = ArticleItem()
article_item['header'] = response.xpath("//head").get()
article_item['title'] = response.xpath("//h1[@id='articleContentId']/text()").get().strip()
article_item['body'] = response.xpath("//article[@class='baidu_pl']").get()
yield article_item
文章目录和文章内容都爬取下来了,接下来需要保存到文件中.爬虫器文件的整体内容如下(csdn_spider.py)
import scrapy
from ..items import CsdnItem,ArticleItem
class CsdnSpiderSpider(scrapy.Spider):
name = 'csdn_spider'
allowed_domains = ['blog.csdn.net']
start_urls = ['http://blog.csdn.net/']
def start_requests(self):
url = 'https://blog.csdn.net/seniorwizard/category_1653109{}.html'
for i in range(1, 5):
if i == 1:
url = url.format("")
else:
url = url.format("_" + str(i))
yield scrapy.Request(
url=url,
callback=self.parse
)
def parse(self, response):
items = CsdnItem()
article_list = response.xpath("//ul[@class='column_article_list']/li")
for article_info in article_list:
items['link'] = article_info.xpath("./a/@href").get()
items['title'] = article_info.xpath("./a/div[@class='column_article_title']/h2/text()").get().strip()
items['description'] = article_info.xpath("./a/div[@class='column_article_desc']/text()").get().strip()
items['post_date'] = article_info.xpath(
"./a/div[@class='column_article_data']/span[2]/text()").get().strip()
yield scrapy.Request(
url=items['link'],
callback=self.parse2
)
def parse2(self, response):
article_item = ArticleItem()
article_item['header'] = response.xpath("//head").get()
article_item['title'] = response.xpath("//h1[@id='articleContentId']/text()").get().strip()
article_item['body'] = response.xpath("//article[@class='baidu_pl']").get()
yield article_item
items.py的内容
import scrapy
class CsdnItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
description = scrapy.Field()
post_date = scrapy.Field()
class ArticleItem(scrapy.Item):
header = scrapy.Field()
title = scrapy.Field()
body = scrapy.Field()
保存网页
网页爬取后保存到本地,并转换成pdf.网页转换成pdf的包,可以选择pdfkit.查了一圈,似乎没找到更好的工具.pdfkit主要有三个接口,from_url, from_string和from_file.在爬取文章目录的时候,初期考虑用pdfkit.from_url,直接读取文章内容,但效率较差,且会将整个页面,包括广告等无关紧要的元素都下载下来,无用信息过多影响观感,因此采用from_string的方式,效率高了很多.pdfkit需要配合wkhtmltopdf才能执行,否则报错,安装教程较多,不在这里说明. 文件的保存在管道中处理,即pipelines.py中
class CsdnPipeline:
def __init__(self):
path_wk = r"C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe"
self.config = pdfkit.configuration(wkhtmltopdf=path_wk)
def process_item(self, item, spider):
output_path = r"E:\project\work\python\spider\csdn\csdn\csdn_article"
filename = item['title'].replace(":", ":")
html = f"""
<!DOCTYPE html>
<html lang="zh-CN" class="">
<head>
<meta charset="utf-8">
<title>{item['title']}</title>
<head>
<body>
{item['body']}
</body>
"""
pdf_file = filename + ".pdf"
pdf_output = os.path.join(output_path, pdf_file)
pdfkit.from_string(html, pdf_output, configuration=self.config,
options={"enable-local-file-access": True})
return item
运行爬虫
scrapy采用命令行的方式运行,执行的方法为
scrapy crawl 爬虫器名称
调试阶段,在pycharm中构建run.py文件,以运行爬虫器
from scrapy import cmdline
cmdline.execute("scrapy crawl csdn_spider".split())
运行效率
利用pdfkit将整个页面下载下来有三种方式,包括from_string,from_url和from_file,from_url将页面所有的内容都做了爬取,整体效率要低上不少,爬取全部页面大概用了2.5个小时,只选取主要内容,用了10分钟,大小也压缩到了四分之一