這篇文章我們將使用 requests 和 xpath 爬取豆瓣電影 Top250,下面先貼上最終的效果圖:

1、網頁分析
(1)分析 URL 規律
我們首先使用 Chrome 瀏覽器打開 豆瓣電影 Top250,很容易可以判斷出網站是一個靜態網頁
然后我們分析網站的 URL 規律,以便於通過構造 URL 獲取網站中所有網頁的內容
首頁:https://movie.douban.com/top250
第二頁:https://movie.douban.com/top250?start=25&filter=
第三頁:https://movie.douban.com/top250?start=50&filter=
...
不難發現,URL 可以泛化為 https://movie.douban.com/top250?start={page}&filter=,其中,page 代表頁數
最后我們還需要驗證一下首頁的 URL 是否也滿足規律,經過驗證,很容易可以發現首頁的 URL 也滿足上面的規律

核心代碼如下:
import requests
# 獲取網頁源代碼
def get_page(url):
# 構造請求頭部
headers = {
'USER-AGENT':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
# 發送請求,獲得響應
response = requests.get(url=url,headers=headers)
# 獲得網頁源代碼
html = response.text
# 返回網頁源代碼
return html
(2)分析內容規律
接下來我們開始分析每一個網頁的內容,並從中提取出需要的數據
使用快捷鍵 Ctrl+Shift+I 打開開發者工具,選中 Elements 選項欄分析網頁的源代碼
需要提取的數據包括(可以使用 xpath 進行匹配):
- 詳細鏈接:
html.xpath('//div[@class="hd"]/a/@href') - 電影名稱:
html.xpath('//div[@class="hd"]/a/span[1]/text()') - 導演/主演、上映年份/國家/分類:
html.xpath('//div[@class="bd"]/p[1]//text()') - 豆瓣評分:
html.xpath('//div[@class="bd"]/div/span[2]/text()') - 評價人數:
html.xpath('//div[@class="bd"]/div/span[4]/text()')

核心代碼如下:
from lxml import etree
# 解析網頁源代碼
def parse_page(html):
# 構造 _Element 對象
html_elem = etree.HTML(html)
# 詳細鏈接
links = html_elem.xpath('//div[@class="hd"]/a/@href')
# 電影名稱
titles = html_elem.xpath('//div[@class="hd"]/a/span[1]/text()')
# 電影信息(導演/主演、上映年份/國家/分類)
infos = html_elem.xpath('//div[@class="bd"]/p[1]//text()')
roles = [j for i,j in enumerate(infos) if i % 2 == 0]
descritions = [j for i,j in enumerate(infos) if i % 2 != 0]
# 豆瓣評分
stars = html_elem.xpath('//div[@class="bd"]/div/span[2]/text()')
# 評論人數
comments = html_elem.xpath('//div[@class="bd"]/div/span[4]/text()')
# 獲得結果
data = zip(links,titles,roles,descritions,stars,comments)
# 返回結果
return data
(3)保存數據
下面將數據分別保存為 txt 文件、json 文件和 csv 文件
import json
import csv
# 打開文件
def openfile(fm):
fd = None
if fm == 'txt':
fd = open('douban.txt','w',encoding='utf-8')
elif fm == 'json':
fd = open('douban.json','w',encoding='utf-8')
elif fm == 'csv':
fd = open('douban.csv','w',encoding='utf-8',newline='')
return fd
# 將數據保存到文件
def save2file(fm,fd,data):
if fm == 'txt':
for item in data:
fd.write('----------------------------------------\n')
fd.write('link:' + str(item[0]) + '\n')
fd.write('title:' + str(item[1]) + '\n')
fd.write('role:' + str(item[2]) + '\n')
fd.write('descrition:' + str(item[3]) + '\n')
fd.write('star:' + str(item[4]) + '\n')
fd.write('comment:' + str(item[5]) + '\n')
if fm == 'json':
temp = ('link','title','role','descrition','star','comment')
for item in data:
json.dump(dict(zip(temp,item)),fd,ensure_ascii=False)
if fm == 'csv':
writer = csv.writer(fd)
for item in data:
writer.writerow(item)
2、編碼實現
下面是完整代碼,也是幾十行可以寫完
import requests
from lxml import etree
import json
import csv
import time
import random
# 獲取網頁源代碼
def get_page(url):
headers = {
'USER-AGENT':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
response = requests.get(url=url,headers=headers)
html = response.text
return html
# 解析網頁源代碼
def parse_page(html):
html_elem = etree.HTML(html)
links = html_elem.xpath('//div[@class="hd"]/a/@href')
titles = html_elem.xpath('//div[@class="hd"]/a/span[1]/text()')
infos = html_elem.xpath('//div[@class="bd"]/p[1]//text()')
roles = [j.strip() for i,j in enumerate(infos) if i % 2 == 0]
descritions = [j.strip() for i,j in enumerate(infos) if i % 2 != 0]
stars = html_elem.xpath('//div[@class="bd"]/div/span[2]/text()')
comments = html_elem.xpath('//div[@class="bd"]/div/span[4]/text()')
data = zip(links,titles,roles,descritions,stars,comments)
return data
# 打開文件
def openfile(fm):
fd = None
if fm == 'txt':
fd = open('douban.txt','w',encoding='utf-8')
elif fm == 'json':
fd = open('douban.json','w',encoding='utf-8')
elif fm == 'csv':
fd = open('douban.csv','w',encoding='utf-8',newline='')
return fd
# 將數據保存到文件
def save2file(fm,fd,data):
if fm == 'txt':
for item in data:
fd.write('----------------------------------------\n')
fd.write('link:' + str(item[0]) + '\n')
fd.write('title:' + str(item[1]) + '\n')
fd.write('role:' + str(item[2]) + '\n')
fd.write('descrition:' + str(item[3]) + '\n')
fd.write('star:' + str(item[4]) + '\n')
fd.write('comment:' + str(item[5]) + '\n')
if fm == 'json':
temp = ('link','title','role','descrition','star','comment')
for item in data:
json.dump(dict(zip(temp,item)),fd,ensure_ascii=False)
if fm == 'csv':
writer = csv.writer(fd)
for item in data:
writer.writerow(item)
# 開始爬取網頁
def crawl():
url = 'https://movie.douban.com/top250?start={page}&filter='
fm = input('請輸入文件保存格式(txt、json、csv):')
while fm!='txt' and fm!='json' and fm!='csv':
fm = input('輸入錯誤,請重新輸入文件保存格式(txt、json、csv):')
fd = openfile(fm)
print('開始爬取')
for page in range(0,250,25):
print('正在爬取第 ' + str(page+1) + ' 頁至第 ' + str(page+25) + ' 頁......')
html = get_page(url.format(page=str(page)))
data = parse_page(html)
save2file(fm,fd,data)
time.sleep(random.random())
fd.close()
print('結束爬取')
if __name__ == '__main__':
crawl()
2020.06.25 補充
最近因為項目需要,也爬取了一下豆瓣讀書,思路和爬取豆瓣電影差不多,這里也把代碼貼出來,供大家參考
import requests
from lxml import etree
from string import Template
import time
import random
import json
import re
def get_page(url):
headers = {
'USER-AGENT':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
response = requests.get(url = url, headers = headers)
html = response.text
return html
def parse_page(html):
html_elem = etree.HTML(html)
# 封面
imgUrl = html_elem.xpath('//tr[@class="item"]/td[1]/a/img/@src')
# 書名
name = html_elem.xpath('//tr[@class="item"]/td[2]/div[@class="pl2"]/a/@title')
# 介紹
info = html_elem.xpath('//tr[@class="item"]/td[2]/p[@class="pl"]/text()')
# 評價
star = html_elem.xpath('//div[contains(@class, "star")]/span[@class="rating_nums"]/text()')
# 評論人數
comments = html_elem.xpath('//div[contains(@class, "star")]/span[@class="pl"]/text()')
# 摘要
quotes = html_elem.xpath('//span[@class="inq"]/text()')
# 將介紹拆分為:作者、譯者、價格、出版日期、出版社
author, translator, price, pubdate, press = [], [], [], [], []
for item in info:
splits = item.split('/')
splits = [item.strip() for item in splits]
# 特判
if item == '[英] 阿·柯南道爾 / 丁鍾華 等 / 群眾出版社 / 1981-8 / 53.00元/68.00元':
author.append(splits[0])
translator.append(splits[1])
press.append(splits[2])
pubdate.append(splits[3])
price.append(splits[4])
# 特判
elif item == 'S.A.阿列克謝耶維奇 / 方祖芳 / 花城出版社/鐵葫蘆圖書 / 2014-6-15 / 34.80元':
author.append(splits[0])
translator.append(splits[1])
press.append(splits[2])
pubdate.append(splits[4])
price.append(splits[5])
# 常規處理
else:
author.append(splits[0])
translator.append('' if len(splits) == 4 else splits[1])
price.append(splits[-1])
pubdate.append(splits[-2])
press.append(splits[-3])
# 匹配整數和浮點數
pattern = r'[+-]?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)([eE][+-]?[0-9]+)?'
star = [float(re.search(pattern, item).group()) for item in star]
comments = [int(re.search(pattern, item.lstrip('(').rstrip(')').strip()).group()) for item in comments]
price = [float(re.search(pattern, item).group()) for item in price]
data = list(zip(imgUrl, name, star, comments, quotes, author, translator, price, pubdate, press))
return data
def save2file(data):
meta = ('imgUrl', 'name', 'star', 'comments', 'quotes', 'author', 'translator', 'price', 'pubdate', 'press')
wrapper = [dict(zip(meta, item)) for item in data]
fd = open('douban.json', 'w', encoding = 'utf-8')
json.dump(wrapper, fd, ensure_ascii = False)
fd.close()
def crawl():
# 網址模板,其參數待替換
init_url = Template('https://book.douban.com/top250?start=$page')
# 所有數據
all_data = []
# 遍歷網頁
for page in range(0, 250, 25):
# 當前網址
curr_url = init_url.substitute(page = str(page))
# 獲取頁面
html = get_page(curr_url)
# 解析頁面
data = parse_page(html)
# 將每一個頁面的數據存起來
all_data.extend(data)
# 隨機睡眠
time.sleep(random.random())
# 一次性將所有數據寫入文件
save2file(all_data)
if __name__ == '__main__':
crawl()
- 網頁分析

- 最終結果

【爬蟲系列相關文章】
