requests 爬虫的通用结构——爬取芬兰网站

写在前面

最近在学习爬虫,发现 requests 这个库在爬取小规模网页的时候,非常好用。用 requests 库爬取了几个芬兰的网站,然后突然发现,可以用一套代码结构来爬取这种网页,于是就来整理了一下。

为了便于理解,我找了其中一个相对简单的网址 赫尔辛基时间 。下面就通过它,来介绍一下 requests 爬虫的通用结构。

Requests 爬虫的基本步骤

利用 requests 库写的爬虫,无外乎下面这几个步骤:

  1. 解析当前的 url,并找出这个 url 页面内所有需要爬取的子链接。
  2. 获取每个超链接页面的详细信息
  3. 保存数据

当然,如果 url 内部还有需要的 url,则需要进行迭代。凡是获取超链接的任务,都算是第一步要做的事,第二步负责提取网页内容,第三步保存数据。

下面看一下爬取赫尔辛基文化 (http://www.helsinkitimes.fi/culture.html) 的代码结构。

1. 获取超链接信息

首先,需要根据这个给出的 url 获取所有需要爬取的 url。因为超链接太多了,我们要筛选出有用的,就必须要找到它们的共同规律,这样才能把它们挑选出来。

这里我们查看网站的源代码,会发现所有文章的 url 链接都是以 “http://www.helsinkitimes.fi/culture/” 开始的,后面是一个四位数的数字,最后以 .html 结尾。

找到规律就很好办了,只要是符合这样结构的超链接,就是我们需要爬取的文章链接。这样我们就可以利用正则表达式,把它们从网站中一一挑出来了。 代码如下:

# 获取页面超链接信息
def get_html(url) :
    html = requests.get(url)
    html.encoding='utf-8'
    soup = BeautifulSoup(html.text, 'lxml')
    for i in soup.find_all('a', href=re.compile(r"/culture/\d{4}[^/]*\.html$")) :
        yield "http://www.helsinkitimes.fi" + i.get('href')

这里我们就可以以 url = http://www.helsinkitimes.fi/culture.html 作为 url 参数。

注意:

yield 是返回每一个满足的超链接,效果上,与返回一个包含所有超链接的 List 是一样的。但利用 yield 就代表该函数是一个生成器,非常节省内存空间,这里不多做解释,感兴趣的可以自行百度查找原因,我们继续爬取网站内容。

2. 获取每个超链接页面详细信息

接下来就是分析每个页面的源码了。一般情况下,每篇文章的 html 结构都是相同的,我们只需要找到相同的结构,把它们分离开来,挑出我们需要的存储起来,就可以了。

思路就这么简单,下面来看代码吧。

因为我们需要将内容变为 json 格式,因此需要先创建一个字典,然后给每个字段赋予相对应的值,我们的字典一共有 6 个键值,定义如下:

data = {'title': '', 'url': '','review': '', 'content': '', 'time': '', 'type': ''}

然后定义两个数组,一个用来存储爬取出来的所有文章信息,另一个用来检测 url 是否重复,避免重复爬取:

dataList = []
url_list = []    # 存储所有访问过的 url, 避免重复访问

然后对每个 url,请求网址,并用 BeautifulSoup 类进行解析:

html_link = requests.get(i)
html_link.encoding='utf-8'
soup = BeautifulSoup(html_link.text, 'lxml')

然后,根据网页源码,找到 6 个键值对应的信息:

url = i
review = ""
content_list = soup.select(".article-content-main")[0].select("p")
content = ""
for j in content_list :
        content = content + str(j)
# 处理内容,替换字符串中的字符,去掉正文中的图片信息
content = contentDeal.deal_content(content)
# 通过标签名查找 时间 
time = soup.select("time")[0]
time = re.search(r"<time datetime=\"(.*)\">", str(time)).group(1)
type = "culture"

这里的 contentDeal 类是我自己定义的一个过滤类,将获取到的信息里面的一些无用信息过滤掉。

最后给字典赋值,添加到数组中,最后返回数组,就可以了:

这个功能是最主要的部分,稍微有点复杂,代码也稍长,但结构还是比较清晰的,整个函数的代码如下:

# 获取超链接页面详细信息
def get_html_link(link_list) :
    # 创建字典
    data = {'title': '', 'url': '','review': '', 'content': '', 'time': '', 'type': ''}
    dataList = []
    url_list = []    # 存储所有访问过的 url, 避免重复访问
    # 遍历所有子链接
    for i in link_list :
        # 判断是否遍历过        
        flag = 0
        for url in url_list :
            if url == str(i) :
                flag = 1
                break
        if flag == 1 :
            continue
        url_list.append(i)
        # 正常请求
        html_link = requests.get(i)
        html_link.encoding='utf-8'
        soup = BeautifulSoup(html_link.text, 'lxml')
        title = soup.title.string
        url = i
        review = ""
        content_list = soup.select(".article-content-main")[0].select("p")
        content = ""
        for j in content_list :
            content = content + str(j)
        # 处理内容,替换字符串中的字符,去掉正文中的图片信息
        content = contentDeal.deal_content(content)
        # 通过标签名查找 时间 
        time = soup.select("time")[0]
        time = re.search(r"<time datetime=\"(.*)\">", str(time)).group(1)
        type = "culture"
        # 给字典赋值
        data['title'] = title
        data['url'] = url
        data['review'] = review
        data['content'] = content
        data['time'] = time
        data['type'] = type
        # 加入 List
        dataList.append(data)
        # 更改字典地址
        data = copy.copy(data)
    return dataList

contentDeal 类:

import re
# 处理字符串文本内容
def deal_content(content) :
    # 将 h1 标签转成 p 标签
    content = re.sub(r"h1>", "p>", content)
    # 将 figcaption 标签转成 p 标签
    content = re.sub(r"figcaption>", "p>", content)
    # 将 p 标签规格化
    content = content.replace("</p>", "<p>")
    content = re.sub(r"<p.*?>", "<p>", content)
    # 将 br 标签变为空格
    content = re.sub(r"<br.*?>", " ", content)
    content = content.replace("</br>", " ")
    # 去除 a 开头的标签
    content = re.sub(r"<a.*?>", "", content)
    content = re.sub(r"</a.*?>", "", content)
    # 去除 b 开头的标签
    content = re.sub(r"<b.*?>", "", content)
    content = re.sub(r"</b.*?>", "", content)
    # 去除 e 开头的标签
    content = re.sub(r"<e.*?>", "", content)
    content = re.sub(r"</e.*?>", "", content)
    # 去除 f 开头的标签
    content = re.sub(r"<f.*?>", "", content)
    content = re.sub(r"</f.*?>", "", content)
    # 去除 i 开头的标签
    content = re.sub(r"<i.*?>", "", content)
    content = re.sub(r"</i.*?>", "", content)
    # 去除 l 开头的标签
    content = re.sub(r"<l.*?>", "", content)
    content = re.sub(r"</l.*?>", "", content)
    # 去除 s 开头的标签
    content = re.sub(r"<s.*?>", "", content)
    content = re.sub(r"</s.*?>", "", content)
    # 去除 t 开头的标签
    content = re.sub(r"<t.*?>", "", content)`在这里插入代码片`
    content = re.sub(r"</t.*?>", "", content)
    # 去除 u 开头的标签
    content = re.sub(r"<u.*?>", "", content)
    content = re.sub(r"</u.*?>", "", content)
    # 去除多余的空格
    content = re.sub(r"\s+", " ", content)
    return content

由于没有评论信息,因此这里的 review 一直是空的,可以忽略这个值。

3. 写回文件

写回文件就简单多了,直接利用最基本的文件操作就可以了,只有一点需要注意的,那就是我们需要保存在 .json 文件中。每一行是一个 json 格式的信息,代码如下:

# 保存数据
def save_data(content_list) :
    with open('../www.helsinkitimes.fi/culture.json', 'a', encoding='utf-8') as f:
        for json_data in content_list :
            f.write(json.dumps(json_data, ensure_ascii=False)+'\n')
            f.flush()

到这里就算基本写完了,最后把它们串在一起就好了。值得庆幸的是,我们这里的网页不是动态进行加载的,而且网页内所有需要的内容,都能在 html 中找到,这样,爬取就比较方便。最难的部分就是获取超链接内容了,但利用 BeautifulSoup 和 正则表达式,也是很容易的。

完整代码

主程序:

import requests, re, json, copy
from bs4 import BeautifulSoup
import contentDeal

# 获取页面超链接信息
def get_html(url) :
    html = requests.get(url)
    html.encoding='utf-8'
    soup = BeautifulSoup(html.text, 'lxml')
    for i in soup.find_all('a', href=re.compile(r"/culture/\d{4}[^/]*\.html$")) :
        yield "http://www.helsinkitimes.fi" + i.get('href')
# 获取超链接页面详细信息
def get_html_link(link_list) :
    # 创建字典
    data = {'title': '', 'url': '','review': '', 'content': '', 'time': '', 'type': ''}
    dataList = []
    url_list = []    # 存储所有访问过的 url, 避免重复访问
    # 遍历所有子链接
    for i in link_list :
        # 判断是否遍历过        
        flag = 0
        for url in url_list :
            if url == str(i) :
                flag = 1
                break
        if flag == 1 :
            continue
        url_list.append(i)
        # 正常请求
        html_link = requests.get(i)
        html_link.encoding='utf-8'
        soup = BeautifulSoup(html_link.text, 'lxml')
        title = soup.title.string
        url = i
        review = ""
        content_list = soup.select(".article-content-main")[0].select("p")
        content = ""
        for j in content_list :
            content = content + str(j)
        # 处理内容,替换字符串中的字符,去掉正文中的图片信息
        content = contentDeal.deal_content(content)
        # 通过标签名查找 时间 
        time = soup.select("time")[0]
        time = re.search(r"<time datetime=\"(.*)\">", str(time)).group(1)
        type = "culture"
        # 给字典赋值
        data['title'] = title
        data['url'] = url
        data['review'] = review
        data['content'] = content
        data['time'] = time
        data['type'] = type
        # 加入 List
        dataList.append(data)
        # 更改字典地址
        data = copy.copy(data)
    return dataList
# 保存数据
def save_data(content_list) :
    with open('../www.helsinkitimes.fi/culture.json', 'a', encoding='utf-8') as f:
        for json_data in content_list :
            f.write(json.dumps(json_data, ensure_ascii=False)+'\n')
            f.flush()
# 函数回调
def fun_call(url) :
    link_list = get_html(url)    # 返回一个生成器
    content_list = get_html_link(link_list)
    save_data(content_list)
# 主函数
def main() :
    url = 'http://www.helsinkitimes.fi/culture.html'
    fun_call(url)
if __name__=='__main__':
    main()

contentDeal 类:

import re
# 处理字符串文本内容
def deal_content(content) :
    # 将 h1 标签转成 p 标签
    content = re.sub(r"h1>", "p>", content)
    # 将 figcaption 标签转成 p 标签
    content = re.sub(r"figcaption>", "p>", content)
    # 将 p 标签规格化
    content = content.replace("</p>", "<p>")
    content = re.sub(r"<p.*?>", "<p>", content)
    # 将 br 标签变为空格
    content = re.sub(r"<br.*?>", " ", content)
    content = content.replace("</br>", " ")
    # 去除 a 开头的标签
    content = re.sub(r"<a.*?>", "", content)
    content = re.sub(r"</a.*?>", "", content)
    # 去除 b 开头的标签
    content = re.sub(r"<b.*?>", "", content)
    content = re.sub(r"</b.*?>", "", content)
    # 去除 e 开头的标签
    content = re.sub(r"<e.*?>", "", content)
    content = re.sub(r"</e.*?>", "", content)
    # 去除 f 开头的标签
    content = re.sub(r"<f.*?>", "", content)
    content = re.sub(r"</f.*?>", "", content)
    # 去除 i 开头的标签
    content = re.sub(r"<i.*?>", "", content)
    content = re.sub(r"</i.*?>", "", content)
    # 去除 l 开头的标签
    content = re.sub(r"<l.*?>", "", content)
    content = re.sub(r"</l.*?>", "", content)
    # 去除 s 开头的标签
    content = re.sub(r"<s.*?>", "", content)
    content = re.sub(r"</s.*?>", "", content)
    # 去除 t 开头的标签
    content = re.sub(r"<t.*?>", "", content)`在这里插入代码片`
    content = re.sub(r"</t.*?>", "", content)
    # 去除 u 开头的标签
    content = re.sub(r"<u.*?>", "", content)
    content = re.sub(r"</u.*?>", "", content)
    # 去除多余的空格
    content = re.sub(r"\s+", " ", content)
    return content

最后,附上项目地址:github地址——爬取 www.helsinkitimes.fi

项目上还有其他几个芬兰网站的爬取代码,由于是刚开始学习,接触的知识比较少,如有任何不对的地方,欢迎指出,谢谢!

参考文章

  1. 爬虫实例–菜鸟教程
  2. 爬虫(进阶),爬取网页信息并写入json文件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值