[python]scrapy爬取CSDN网页并转换成PDF

413 阅读4分钟

最近闲着没事,学习一下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分钟,大小也压缩到了四分之一