效果图:
爬取电影资料 源代码:
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): #一次加 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()
爬取电影评论 源代码:
import requests
from lxml import etree
import re
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 parse4link(html,base_url):
link = None
html_elem = etree.HTML(html)
url = html_elem.xpath('//div[@id="paginator"]/a[@class="next"]/@href')
if url:
link = base_url + url[0]
return link
# 解析网页源代码,获取数据
def parse4data(html):
html = etree.HTML(html)
agrees = html.xpath('//div[@class="comment-item"]/div[2]/h3/span[1]/span/text()')
authods = html.xpath('//div[@class="comment-item"]/div[2]/h3/span[2]/a/text()')
stars = html.xpath('//div[@class="comment-item"]/div[2]/h3/span[2]/span[2]/@title')
contents = html.xpath('//div[@class="comment-item"]/div[2]/p/span/text()')
data = zip(agrees,authods,stars,contents)
return data
# 打开文件
def openfile(fm):
fd = None
if fm == 'txt':
fd = open('douban_comment.txt','w',encoding='utf-8')
elif fm == 'json':
fd = open('douban_comment.json','w',encoding='utf-8')
elif fm == 'csv':
fd = open('douban_comment.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('agree:' + str(item[0]) + '\n')
fd.write('authod:' + str(item[1]) + '\n')
fd.write('star:' + str(item[2]) + '\n')
fd.write('content:' + str(item[3]) + '\n')
if fm == 'json':
temp = ('agree','authod','star','content')
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():
moveID = input('请输入电影ID:')
while not re.match(r'\d{8}',moveID): #正则表达式验证是否输入正确
moveID = input('输入错误,请重新输入电影ID:')
base_url = 'https://movie.douban.com/subject/' + moveID + '/comments'
fm = input('请输入文件保存格式(txt、json、csv):')
while fm!='txt' and fm!='json' and fm!='csv':
fm = input('输入错误,请重新输入文件保存格式(txt、json、csv):')
fd = openfile(fm)
print('开始爬取')
link = base_url
while link:
print('正在爬取 ' + str(link) + ' ......')
html = get_page(link)
link = parse4link(html,base_url)
data = parse4data(html)
save2file(fm,fd,data)
time.sleep(random.random())
fd.close()
print('结束爬取')
if __name__ == '__main__':
crawl()
讲解:
一、requests 库
相比 python 自带的 urllib 库,需要下载的 requests 库功能强大、简单易用
(注意:别和 urllib 库的 request 模块的 Request 方法搞混淆)
更方便的获取网页源代码
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
用到了 get()
和 text()
函数,获取网页源代码,代码长度与之前相比明显更短了(函数不解析,明白在哪用即可)
二、lxml 库的 etree 模块
1、.HTML 方法
构造 lxml.etree._Element 对象,为后面的方法做准备
2、.xpath 方法(另类的正则表达式)
豆瓣网页源代码中的一段:
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.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
相关匹配语法可以跳转到结尾的博客参考,下面重点讲讲这两个句子
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]
strip()
方法用于移除字符串头尾的空格和换行符
.xpath
方法返回列表
enumerate()
方法作用于列表,从 0 开始与列表中的元素一一对应
i 即为一一对应的数字,j 即为对应的元素(字符串)
而 roles 和 descritions 的区别就是但对应数字为奇数时上面打印,反之下面打印
具体实例:
网页源代码:
导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...<br/>
1994 / 美国 / 犯罪 剧情
打印:
role:导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...
descrition:1994 / 美国 / 犯罪 剧情
# 说明 <br/>是一个分隔符号,用于列表分隔元素
三、跳转页面
以往是根据 URL 的规律来进行页数的爬取,但我们这次的有所不同,来看源代码
def parse4link(html,base_url):
link = None
html_elem = etree.HTML(html)
url = html_elem.xpath('//div[@id="paginator"]/a[@class="next"]/@href')
if url:
link = base_url + url[0] # 加上匹配到的地址
return link
# 我们可以发现评论首页是单独地址,从第二页开始才变得有规律(首页地址 + 匹配地址)
参考博客:
爬虫系列(七) requests的基本使用
爬虫系列(九) xpath的基本使用
爬虫系列(十) 用requests和xpath爬取豆瓣电影
爬虫系列(十一) 用requests和xpath爬取豆瓣电影评论