網頁的結構:
我們通過一個最簡單的網頁來分析網頁的結構。
https://morvanzhou.github.io/static/scraping/basic-structure.html
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<title>Scraping tutorial 1 | 莫煩Python</title>
<link rel="icon" href="https://morvanzhou.github.io/static/img/description/tab_icon.png">
</head>
<body>
<h1>爬蟲測試1</h1>
<p>
這是一個在 <a href="https://morvanzhou.github.io/">莫煩Python</a>
<a href="https://morvanzhou.github.io/tutorials/data-manipulation/scraping/">爬蟲教程</a> 中的簡單測試.
</p>
</body>
</html>
HTML網頁中上所有的實體內容, 都會有個 tag 來框住它。網頁的主體的tag分成兩部分, 即header
和 body。
header
中, 存放着一些網頁的元信息, 比如title最終是顯示在網頁標籤上的。
如:
<title>Scraping tutorial 1 | 莫煩Python</title>
body存放的是網頁的主體信息。如heading
, 視頻, 圖片和文字等。
<h1></h1>
tag是主標題, 我們看到呈現出來的效果就是大一號的文字。
如:
<h1>爬蟲測試1</h1>
<p></p>
裏面的文字就是一個段落。<a></a>
裏面都是鏈接。
如:
這是一個在 <a href="https://morvanzhou.github.io/">莫煩Python</a>
<a href="https://morvanzhou.github.io/tutorials/data-manipulation/scraping/">爬蟲教程</a> 中的簡單測試.
使用urlopen()爬取網頁內容:
如:
from urllib.request import urlopen
html = urlopen(
"https://morvanzhou.github.io/static/scraping/basic-structure.html"
).read().decode('utf-8')
# 如果網頁中含有中文,則decode()參數爲'utf-8
print(html)
運行結果如下:
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<title>Scraping tutorial 1 | 莫煩Python</title>
<link rel="icon" href="https://morvanzhou.github.io/static/img/description/tab_icon.png">
</head>
<body>
<h1>爬蟲測試1</h1>
<p>
這是一個在 <a href="https://morvanzhou.github.io/">莫煩Python</a>
<a href="https://morvanzhou.github.io/tutorials/data-manipulation/scraping/">爬蟲教程</a> 中的簡單測試.
</p>
</body>
</html>
可以看到我們將網頁的源代碼下載了下來。然後我們要將下載下來的源代碼作爲參數提供給BeautifulSoup,讓BeautifulSoup來做匹配,找出其中我們需要的信息。
常用正則表達式簡介:
^匹配字符串的開頭。
$匹配字符串的末尾。
/順斜槓是表示表達式開始和結束的“定界符”。
\反斜槓是表示轉義字符。
.是匹配任意字符的意思。
“+”表示前面表達式匹配一次至多次(比如.+即匹配任意字符一次或多次)。
*表示匹配前一個字符0次或無限次(貪婪匹配)。
?表示匹配前一個字符0次或1次(非貪婪匹配,最小匹配)。
(.+)默認是貪婪匹配。.+?表示匹配任意字符並最小匹配。(.+?)加上括號後表示匹配到的對象設置成多個分組(如果匹配到了多個對象)。
如:
<a href="xxx"><span>
如果用<.+>匹配,則匹配結果是
<a href="xxx"><span>
如果用<.+?>匹配,則匹配結果是
<a href="xxx">
.* 就是單個字符匹配任意次,即貪婪匹配。.*? 表示匹配滿足前後條件時只匹配最小範圍的情況,即最小匹配。
如:
<H1>Chapter 1 - 介紹正則表達式</H1>
使用/<.*>/匹配的結果爲:
H1>Chapter 1 - 介紹正則表達式</H1
使用/<.*?>/匹配結果爲:
H1
使用正則表達式匹配抓取網頁內容:
實際上,如果是初級的匹配,只要使用正則表達式來匹配就可以了。我們先看看用正則表達式如何匹配。
正則表達式匹配抓取標籤內容:
from urllib.request import urlopen
import re
html = urlopen(
"https://morvanzhou.github.io/static/scraping/basic-structure.html"
).read().decode('utf-8')
# 如果網頁中含有中文,則decode()參數爲'utf-8
res = re.findall(r"<title>(.+?)</title>", html)
# 上面下載下來的網頁內容html作爲匹配源
print("\n 本網頁的title是", res)
print("\n 本網頁的title是", res[0])
運行結果如下:
本網頁的title是 ['Scraping tutorial 1 | 莫煩Python']
本網頁的title是 Scraping tutorial 1 | 莫煩Python
正則表達式匹配抓取段落內容:
如果想要找到中間的那個段落 <p>
, 因爲段落在 HTML 中還夾雜着 tab, new line, 我們給一個flags=re.DOTALL
來對這些 tab, new line不敏感。
from urllib.request import urlopen
import re
html = urlopen(
"https://morvanzhou.github.io/static/scraping/basic-structure.html"
).read().decode('utf-8')
# 如果網頁中含有中文,則decode()參數爲'utf-8
res = re.findall(r"<p>(.*?)</p>", html, flags=re.DOTALL)
# 上面下載下來的網頁內容html作爲匹配源
print("\n 本網頁的段落是", res)
print("\n 本網頁的段落是", res[0])
運行結果如下:
本網頁的段落是 ['\n\t\t這是一個在 <a href="https://morvanzhou.github.io/">莫煩Python</a>\n\t\t<a href="https://morvanzhou.github.io/tutorials/data-manipulation/scraping/">爬蟲教程</a> 中的簡單測試.\n\t']
本網頁的段落是
這是一個在 <a href="https://morvanzhou.github.io/">莫煩Python</a>
<a href="https://morvanzhou.github.io/tutorials/data-manipulation/scraping/">爬蟲教程</a> 中的簡單測試.
正則表達式匹配抓取鏈接:
from urllib.request import urlopen
import re
html = urlopen(
"https://morvanzhou.github.io/static/scraping/basic-structure.html"
).read().decode('utf-8')
# 如果網頁中含有中文,則decode()參數爲'utf-8
res = re.findall(r'href="(.*?)"', html)
# 上面下載下來的網頁內容html作爲匹配源
print("\n 本網頁的title是", res)
print("\n 本網頁的title是", res[0])
運行結果如下:
本網頁的鏈接是 ['https://morvanzhou.github.io/static/img/description/tab_icon.png', 'https://morvanzhou.github.io/', 'https://morvanzhou.github.io/tutorials/data-manipulation/scraping/']
本網頁的鏈接是 https://morvanzhou.github.io/static/img/description/tab_icon.png
使用BeautiSoup匹配抓取網頁內容:
BeautifulSoup的安裝方法:
python3 -m pip install beautifulsoup4
BeautifulSoup匹配抓取網頁內容的步驟:
選擇要爬取的網址 (url);
使用urlopen等登錄這個網址,read() 讀取網頁信息;
將讀取的信息放入BeautifulSoup,使用 BeautifulSoup(代替正則表達式)匹配你需要抓取的信息。
BeautifulSoup解析基礎網頁:
from bs4 import BeautifulSoup
from urllib.request import urlopen
html = urlopen("https://morvanzhou.github.io/static/scraping/basic-structure.html").read().decode('utf-8')
soup = BeautifulSoup(html, features='lxml')
# 現在用BeautifulSoup代替正則表達式匹配來找出title和段落等部分,features='lxml'即選擇lxml爲解析器
print(soup.h1)
# 返回title
print('\n', soup.p)
# 返回段落
all_href = soup.find_all('a')
# 找到所有a標籤
print(all_href)
# 這時候我們找到的是包含<a,</a>的鏈接
all_href = [l['href'] for l in all_href]
# 生成器寫法
# 上面這句等於:
# for l in all_href:
# print(l['href'])
# 上面相當於打印出字典中key=href對應的value值即鏈接
print(all_href)
# 這時候找到的就只是鏈接了,去掉了頭尾的<a,</a>等
運行結果如下:
<h1>爬蟲測試1</h1>
<p>
這是一個在 <a href="https://morvanzhou.github.io/">莫煩Python</a>
<a href="https://morvanzhou.github.io/tutorials/data-manipulation/scraping/">爬蟲教程</a> 中的簡單測試.
</p>
[<a href="https://morvanzhou.github.io/">莫煩Python</a>, <a href="https://morvanzhou.github.io/tutorials/data-manipulation/scraping/">爬蟲教程</a>]
['https://morvanzhou.github.io/', 'https://morvanzhou.github.io/tutorials/data-manipulation/scraping/']
BeautifulSoup解析CSS網頁:
CSS可以起到分別裝飾每一個網頁部件的作用。在裝飾每一個網頁部件的時候, 都會給它一個名字。同一種類型的部件, 名字都可以一樣。部件裏面的字體/背景顏色, 字體大小, 都是由 CSS 來控制。
先看一個使用CSS的網頁結構:
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<title>爬蟲練習 列表 class | 莫煩 Python</title>
<style>
.jan {
background-color: yellow;
}
.feb {
font-size: 25px;
}
.month {
color: red;
}
</style>
</head>
<body>
<h1>列表 爬蟲練習</h1>
<p>這是一個在 <a href="https://morvanzhou.github.io/" >莫煩 Python</a> 的 <a href="https://morvanzhou.github.io/tutorials/data-manipulation/scraping/" >爬蟲教程</a>
裏無敵簡單的網頁, 所有的 code 讓你一目瞭然, 清晰無比.</p>
<ul>
<li class="month">一月</li>
<ul class="jan">
<li>一月一號</li>
<li>一月二號</li>
<li>一月三號</li>
</ul>
<li class="feb month">二月</li>
<li class="month">三月</li>
<li class="month">四月</li>
<li class="month">五月</li>
</ul>
</body>
</html>
如上例,<head></head>中的<style></style>這段代碼就是我們的CSS代碼,這裏由於比較簡單直接寫在了<head></head>中,正常情況下這段代碼會單獨保存成一個CSS文件。
在<ul></ul>中我們可以看到元素是以class來分類保存的。
我們可以用BeautifulSoup通過CSS的class分類來抓取我們需要的信息。
from bs4 import BeautifulSoup
from urllib.request import urlopen
html = urlopen("https://morvanzhou.github.io/static/scraping/list.html").read().decode('utf-8')
soup = BeautifulSoup(html, features='lxml')
# 現在用BeautifulSoup代替正則表達式匹配來找出title和段落等部分,features='lxml'即選擇lxml爲解析器
month = soup.find_all('li', {'class': 'month'})
# 如果只傳入'li'則所有'li'都會被選中,加上{'class':'month'}後所有li中的month類纔會被選中
for m in month:
print(m.get_text())
print(m)
# 上面兩句print的區別是前一句只會打出<li></li>之間的內容,而後一句會打出包括<li></li>的內容
jan = soup.find('ul', {'class': 'jan'})
d_jan = jan.find_all('li')
for d in d_jan:
print(d.get_text())
# 打印出jan中的所有日期
運行結果如下:
一月
<li class="month">一月</li>
二月
<li class="feb month">二月</li>
三月
<li class="month">三月</li>
四月
<li class="month">四月</li>
五月
<li class="month">五月</li>
一月一號
一月二號
一月三號
BeautifulSoup搭配正則表達式匹配解析網頁:
from bs4 import BeautifulSoup
from urllib.request import urlopen
import re
html = urlopen("https://morvanzhou.github.io/static/scraping/table.html").read().decode('utf-8')
soup = BeautifulSoup(html, features='lxml')
# 現在用BeautifulSoup代替正則表達式匹配來找出title和段落等部分,features='lxml'即選擇lxml爲解析器
# 我們現在想找出網頁中的圖片鏈接
img_links = soup.find_all("img", {"src": re.compile('.*?\.jpg')})
# re.compile函數用於編譯正則表達式,生成一個正則表達式(Pattern)對象。
for link in img_links:
print(link['src'])
# 找到不是圖片的其他鏈接
course_links = soup.find_all('a', {'href': re.compile('https://morvan.*')})
for link in course_links:
print(link['href'])
運行結果如下:
from bs4 import BeautifulSoup
from urllib.request import urlopen
import re
html = urlopen("https://morvanzhou.github.io/static/scraping/table.html").read().decode('utf-8')
soup = BeautifulSoup(html, features='lxml')
# 現在用BeautifulSoup代替正則表達式匹配來找出title和段落等部分,features='lxml'即選擇lxml爲解析器
# 我們現在想找出網頁中的圖片鏈接
img_links = soup.find_all("img", {"src": re.compile('.*?\.jpg')})
# re.compile函數用於編譯正則表達式,生成一個正則表達式(Pattern)對象。
for link in img_links:
print(link['src'])
# 找到不是圖片的其他鏈接
course_links = soup.find_all('a', {'href': re.compile('https://morvan.*')})
for link in course_links:
print(link['href'])
練習:爬取百度百科
from bs4 import BeautifulSoup
from urllib.request import urlopen
import re
import random
import time
base_url = "https://baike.baidu.com"
his = ["/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711"]
for i in range(20):
# 循環爬20次
time.sleep(2)
# 每爬一次歇兩秒,避免百度檢測到你的爬蟲
url = base_url + his[-1]
# 這裏url分成了兩部分,his[-1]表示his列表中的最後一項
html = urlopen(url).read().decode('utf-8')
soup = BeautifulSoup(html, features='lxml')
print(soup.find('h1').get_text(), ' url: ', his[-1])
# 該網站網絡爬蟲四個字是h1標籤,並打印其鏈接
# 我們再找這個網頁中所有百度百科的鏈接
sub_urls = soup.find_all("a", {"target": "_blank", "href": re.compile("/item/(%.{2})+$")})
# sub_urls獲得該網頁所有其他百度百科的鏈接,_blank放在一個鏈接中表示瀏覽器另外打開一個新窗口,(%.{2})+這樣設置正則表達式,我們找到的網址就都是 /item/%xx%xx%xx...這樣的格式了。
if len(sub_urls) != 0:
his.append(random.sample(sub_urls, 1)[0]['href'])
# 從sub_urls list中隨機獲取1個元素,作爲一個片斷返回,添加到his中
else:
his.pop()
運行結果如下:
網絡爬蟲 url: /item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711
寬度優先搜索 url: /item/%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2
隊列 url: /item/%E9%98%9F%E5%88%97
數組 url: /item/%E6%95%B0%E7%BB%84
字節 url: /item/%E5%AD%97%E8%8A%82
亂碼 url: /item/%E4%B9%B1%E7%A0%81
電子郵件程序 url: /item/%E7%94%B5%E5%AD%90%E9%82%AE%E4%BB%B6%E7%A8%8B%E5%BA%8F
點擊 url: /item/%E7%82%B9%E5%87%BB
網絡營銷 url: /item/%E7%BD%91%E7%BB%9C%E8%90%A5%E9%94%80
百度百科:多義詞 url: /item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91%EF%BC%9A%E5%A4%9A%E4%B9%89%E8%AF%8D
趙氏孤兒大報仇 url: /item/%E8%B5%B5%E6%B0%8F%E5%AD%A4%E5%84%BF
義項 url: /item/%E4%B9%89%E9%A1%B9
趙氏孤兒大報仇 url: /item/%E8%B5%B5%E6%B0%8F%E5%AD%A4%E5%84%BF
百度百科:多義詞 url: /item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91%EF%BC%9A%E5%A4%9A%E4%B9%89%E8%AF%8D
趙氏孤兒大報仇 url: /item/%E8%B5%B5%E6%B0%8F%E5%AD%A4%E5%84%BF
義項 url: /item/%E4%B9%89%E9%A1%B9
約翰內斯·勃拉姆斯 url: /item/%E7%BA%A6%E7%BF%B0%E5%A5%88%E6%96%AF%C2%B7%E5%8B%83%E6%8B%89%E5%A7%86%E6%96%AF
約瑟夫·約阿希姆 url: /item/%E7%BA%A6%E7%91%9F%E5%A4%AB%C2%B7%E7%BA%A6%E9%98%BF%E5%B8%8C%E5%A7%86
種族歧視 url: /item/%E7%A7%8D%E6%97%8F%E6%AD%A7%E8%A7%86
美國最高法院 url: /item/%E7%BE%8E%E5%9B%BD%E6%9C%80%E9%AB%98%E6%B3%95%E9%99%A2
Process finished with exit code 0