春满大地,富贵花开。微雨众卉新,一雷惊蛰始。敬请关注微信公众号:AiryData。

【进阶】Python爬虫采集整个网站

PYTHON Airy 3334℃ 0评论

前言

在之前的文章中Python实现“维基百科六度分隔理论“之基础爬虫 ,我们实现了在一个网站上随机地从一个链接到另一个链接,但是,如果我们需要系统地把整个网站按目录分类,或者要搜索网站上的每一个页面,我们该怎么办?我们需要采集整个网站,但是那是一种非常耗费内存资源的过程,尤其是处理大型网站时,比较合适的工具就是用一个数据库来存储采集的资源,之前也说过。下面来说一下怎么做。

生成网站地图sitemap

网站地图,又称站点地图,它就是一个页面,上面放置了网站上需要搜索引擎抓取的所有页面的链接(注:不是所有页面,一般来说是所有文章链接,比如我的http://www.airyyun.com/xmlsitemap.xml)。大多数人在网站上找不到自己所需要的信息时,可能会将网站地图作为一种补救措施。搜索引擎蜘蛛非常喜欢网站地图。

对于SEO,网站地图的好处:

1.为搜索引擎蜘蛛提供可以浏览整个网站的链接简单的体现出网站的整体框架出来给搜索引擎看;

2.为搜索引擎蜘蛛提供一些链接,指向动态页面或者采用其他方法比较难以到达的页面;

3.作为一种潜在的着陆页面,可以为搜索流量进行优化;

4.如果访问者试图访问网站所在域内并不存在的URL,那么这个访问者就会被转到“无法找到文件”的错误页面,而网站地图可以作为该页面的“准”内容。

采集数据

采集网站数据并不难,但是需要爬虫有足够的深度。我们创建一个爬虫,递归地遍历每个网站,只收集那些网站页面上的数据。一般的比较费时间的网站采集方法从顶级页面开始(一般是网站主页),然后搜索页面上的所有链接,形成列表,再去采集到的这些链接页面,继续采集每个页面的链接形成新的列表,重复执行。

很明显,这是一个复杂度增长很快的过程。加入每个页面有10个链接,网站上有5个页面深度,如果采集整个网站,一共得采集的网页数量是105,即100000个页面。

因为网站的内链有很多都是重复的,所以为了避免重复采集,必须链接去重,在Python中,去重最常用的方法就是使用自带的set集合方法。只有“新”链接才会被采集。看一下代码实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re

pages = set()
def getLinks(pageurl):
    global pages
    html = urlopen("http://en.wikipedia.org" + pageurl)
    soup = BeautifulSoup(html)
    for link in soup.findAll("a", href=re.compile("^(/wiki/)")):
        if 'href' in link.attrs:
            if link.attrs['href'] not in pages:
                #这是新页面
                newPage = link.attrs['href']
                print(newPage)
                pages.add(newPage)
                getLinks(newPage)

getLinks("")

原理说明:程序执行时,用函数处理一个空URL,其实就是维基百科的主页,然后遍历首页上每个链接,并检查是否已经在全局变量集合pages里面,如果不在,就打印并添加到pages集合,然后递归处理这个链接。

递归警告:Python默认的递归限制是1000次,因为维基百科的链接浩如烟海,所以这个程序达到递归限制后就会停止。如果你不想让它停止,你可以设置一个递归计数器或者其他方法。

采集整个网站数据

为了有效使用爬虫,在用爬虫的时候我们需要在页面上做一些事情。我们来创建一个爬虫来收集页面标题、正文的第一个段落,以及编辑页面的链接(如果有的话)这些信息。

第一步,我们需要先观察网站上的页面,然后制定采集模式,通过F12(一般情况下)审查元素,即可看到页面组成。

观察维基百科页面,包括词条和非词条页面,比如隐私策略之类的页面,可以得出下面的规则:

  • 所有的标题都是在h1→span标签里,而且页面上只有一个h1标签。
  • 所有的正文文字都在div#bodyContent标签里,如果我们想获取第一段文字,可以用div#mw-content-text→p,除了文件页面,这个规则对所有页面都适用。
  • 编辑链接只出现在词条页面上,如果有编辑链接,都位于li#ca-edit标签的li#ca-edit→span→a里面。

调整一下之前的代码,我们可以建立一个爬虫和数据采集的组合程序,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re

pages = set()
def getLinks(pageUrl):
    global pages
    html = urlopen("http://en.wikipedia.org" + pageUrl)
    soup = BeautifulSoup(html)
    try:
        print(soup.h1.get_text())
        print(soup.find(id="mw-content-text").findAll("p")[0])
        print(soup.find(id="ca-edit").find("span").find("a").attrs['href'])
    except AttributeError:
        print("页面缺少属性")
   
    for link in soup.findAll("a", href = re.compile("^(/wiki/)")):
        if 'href' in link.attrs:
            #这是新页面
            newPage = link.attrs['href']
            print("------------------\n"+newPage)
            pages.add(newPage)
            getLinks(newPage)
getLinks("")

这个for循环和原来的采集程序基本上是一样的,因为不能确定每一页上都有所有类型的数据,所以每个打印语句都是按照数据在页面上出现的可能性从高到低排列的。

数据存储到MySQL

前面已经获取了数据,直接打印出来,查看比较麻烦,所以我们就直接存到MySQL里面吧,这里只存链接没有意义,所以我们就存储页面的标题和内容。前面我有两篇文章已经介绍过如何存储数据到MySQL,数据表是pages,这里直接给出代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
import datetime
import random
import pymysql

conn = pymysql.connect(host = '127.0.0.1', port = 3306, user = 'root', passwd = '19930319', db = 'wiki', charset = 'utf8mb4')

cur = conn.cursor()
cur.execute("USE wiki")

#随机数种子
random.seed(datetime.datetime.now())

#数据存储
def store(title, content):
    cur.execute("INSERT INTO pages(title, content) VALUES("%s", "%s")", (title, content))
    cur.connection.commit()

def getLinks(articleUrl):
    html = urlopen("http://en.wikipedia.org" + articleUrl)
    soup = BeautifulSoup(html)
    title = soup.find("h1").get_text()
    content  = soup.find("div", {"id":"mw-content-text"}).find("p").get_text()
    store(title, content)
    return soup.find("div", {"id":"bodyContent"}).findAll("a", href=re.compile("^(/wiki/)((?!:).)*$"))
#设置第一页
links = getLinks("/wiki/Kevin_Bacon")
try:
    while len(links)>0:
        newArticle = links[random.randint(0, len(links)-1)].attrs['href']
        print (newArticle)
        links = getLinks(newArticle)
finally:
    cur.close()
    conn.close()

小结

今天主要讲一下Python中遍历采集一个网站的链接,方便下面的学习。

希望通过上面的操作能帮助大家。如果你有什么好的意见,建议,或者有不同的看法,我都希望你留言和我们进行交流、讨论。

如果想快速联系我,欢迎关注微信公众号:AiryData。

转载请注明:数据之美 » 【进阶】Python爬虫采集整个网站

喜欢 (7)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址