Press "Enter" to skip to content

使用Python爬虫抓取网站的内容

背景

一直非常喜欢一个网站 https://www.examples.com/,想着这个网站如果突然不在了可怎么办?就想着有什么办法能帮这个网站保存下来。

正好之前玩过Python爬虫,想着能不能帮网站爬下来,试试吧?

网站爬取

分析

我之前做爬虫一般需要分2步骤的,第一步是拿到要抓取目标站点的页面url,这一般都是独立算法的,第二步是抓取页面内容,然后分析页面内容拿到想要的数据。

  • 获取完整网站地图

获取网站地图,我之前都是爬虫自动爬,就是给爬虫一个入口,然后分析这个入口页面的超链接,拿到所有的URL,然后判断下是不是本站的(防止爬虫跳到别的站点去了),然后去重之后放到一个集合里面,最后再看是深度优先还是广度优先递归的进行链接爬虫即可。

这样你就能拿到这个网站所有的URL数据库了。

但是我发现examples这个网站他自己有个自己站点所有文章的集合页 full_archive,我靠这就很简单了啊,因为我们其实爬虫最麻烦的就是爬URL的过程,这样一来我只要分析这个页面就能拿到这个网站的所有页面了。

打开这个页面,右键查看源文件,全选保存为本地HTML文件。

我写了个脚本,先贴上来再分析。

# -*- coding: utf-8 -*-
# import requests

from html.parser import HTMLParser
import json

hrefs = []

class MyHTMLParser(HTMLParser):
    _is_archive_list = False
    _is_a_tag = False
    def handle_starttag(self, tag, attrs):
        if len(attrs) == 1:
            if attrs[0][1] == "bca-archive__monthlisting":
                self._is_archive_list = True
        if tag == "a" and self._is_archive_list:
            self._is_a_tag = True
            print(attrs[0][1])
            hrefs.append("https://www.examples.com" + attrs[0][1])

    def handle_endtag(self, tag):
        if tag == "ul":
            self._is_archive_list = False
        if tag == "a":
            self._is_a_tag = False

    def handle_data(self, data):
        if self._is_archive_list and self._is_a_tag:
            print("Data: ", data)

parser = MyHTMLParser()
f = open("full_archive.html", "r")
full_archive = f.read()
parser.feed(full_archive)
f.close()

jsonString = json.dumps(hrefs)
jsonFile = open("hrefs.json", "w")
jsonFile.write(jsonString)
jsonFile.close()
print("hrefs len: " + str(len(hrefs)))

因为要分析HTML文档,用到了一个HTMLParser库,要装一下。

这个脚本主要是用HTMLParser库来分析页面,最好再把分析出来的URL存到本地一个json文件里面。

关键就是HTMLParser这个库的使用,很多年前用过,忘记怎么用的了。然后我随便看看官网,大概又想起来怎么用的了。

你要先parser = MyHTMLParser() ,拿到他的这个parser对象,然后帮你的html丢给他的feed方法就醒了。这个MyHTMLParser东西呢,是要继承HTMLParser这个函数的,然后他有几个方法让你重写,handle_starttag、handle_endtag、handle_data分别是当开始处理一个标签,结束处理一个标签和处理一个标签的内容。

这个玩意的小诀窍是你要定义一些flag,比如上面例子里面,开始一个标签 或者结束一个标签,判断下标签属性,如果是你要关注的,就打开这个flag。然后在别的地方就可以通过判断这个flag是否开启来决定要怎么处理数据了。

这样一来,我们就获取了一个包含这个网站所有页面的URL数据的hrefs.json文件了。

  • 获取网站所有页面

那网站所有的页面URL数据都有了怎么来获取这个网站的页面数据呢?

这里提供下我写的另一个脚本

# -*- coding: utf-8 -*-

import cfscrape
import json
import os
from time import sleep

f = open("hrefs.json", "r")
hrefs = json.load(f)
print(len(hrefs))

scraper = cfscrape.create_scraper()
i=0

for href in hrefs:
    i=i+1
    print(str(i)+": "+href)
    fileName = href[25:]
    filePath = "data/"+fileName+".html"
    os.makedirs(os.path.dirname(filePath), exist_ok=True)

    content = scraper.get(href).content
    f = open(filePath, "a")
    f.write(str(content, 'UTF-8'))
    f.close()
    sleep(0.1)
f.close()

这个页面很简单,就是打开我们刚才保存链接的json文件,然后循环里面的URL数据,然后下载下来存到本地。

值得一提的时,我这里下载他们网站使用了一个叫cfscrape库,我的这篇文章有介绍,因为他们网站被Cloudflare保护的,你直接用request什么的库是没法帮网站下载下来的。

  • 分析页面数据

这样所有我们想要的页面数据都下载到我们本地一个叫data的目录里面了,我们现在要做的就是遍历我们的文件目录,然后一个一个解析里面的数据了。

老规矩,直接上脚本

# -*- coding: utf-8 -*-

import glob
from html.parser import HTMLParser
import json
from bs4 import BeautifulSoup

articles = []
for filename in glob.iglob('data/**', recursive=True):
    article={}

    f = open(filename, "r")
    full_archive = f.read()
    soup = BeautifulSoup(full_archive, 'html.parser')

    #title
    article['title'] = soup.find_all("h1", {"class": "single-title entry-title"})[0].contents[0]

    #categories
    categories = []
    for tag in soup.find_all("a", {"rel": "category tag"}):
        categories.append({"href":tag['href'], "text":tag.text.strip()})
    article['categories'] = categories

    #tags
    tags = []
    for tag in soup.find_all("a", {"rel": "post tag"}):
        tags.append({"href":tag['href'], "text":tag.text.strip()})
    article['tags'] = tags

    #updated
    article['updated'] = soup.find_all("span", {"class": "updated"})[0].contents[0]

    #content
    article['content'] = soup.find_all("section", {"class": "post-content clearfix"})

    articles.append(article)

    f.close()

这里同样是要分析HTML文档,我用了个更省事的框架 BeautifulSoup4,看代码就知道了,他直接根据标签属性进行数据抓取,非常方便,最后拼装成我们要的数据放到我们的数组里面,就拿到所有我们要的数据了。

再根据我们的需要将这些格式化的数据持久化到本地文档就行啦。

注意:

https://www.examples.com/ 这其实是一个不存在的虚构网站,我真正抓取的站点没有贴出来,以防止一些不好的事情发生。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注