本文仅涉及一些网页爬取工具的简单应用。利用python的
urllib、requests、BeautifulSoup
等库尝试实现了简单的爬取百度图片、必应图片/每日壁纸、豆瓣评论等功能。
一、工具库介绍
1. urllib 包
- 介绍:Python urllib 库用于操作网页 URL,并对网页的内容进行抓取处理
- urllib—URL处理模块
- 模块:
- urllib.request - 打开和读取 URL。
- urllib.error - 包含 urllib.request 抛出的异常。
- urllib.parse - 解析 URL。
- urllib.robotparser - 解析 robots.txt 文件。
2. requests 库
- 介绍:基于urllib,采⽤Apache2 Licensed开源协议的 HTTP 库
- 官方文档
- 主要有7个方法
- Requests库主要有7个方法,分别是
- requests.request():构造一个请求,支撑以下各方法的基础方法
- requests.get():获取HTML网页的主要方法,对应于HTTP的GET
- requests.head():获取HTML网页头信息的方法,对应于HTTP的HEAD
- requests.post():向HTML网页提交POST请求的方法,对应于HTTP的POST
- requests.put():向HTML网页提交PUT请求的方法,对应于HTTP的PUT
- requests.patch():向HTML网页提交局部修改请求,对应于HTTP的PATCH
- requests.delete():向HTML页面提交删除请求,对应于HTTP的DELETE
- Requests库主要有7个方法,分别是
3. BeautifulSoup
- 介绍:提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据。
- 文档
二、爬取百度/必应图片
1. 分析下载链接
打开百度图片,输入关键词如”风景壁纸”,开发者工具->Network->抓包工具XHR。鼠标向下滑动,可得到图片的请求报文
- 这里
https://image.baidu.com/search/acjson?
是我们需要的链接,问号后面的都是对应的参数信息
- 这里
同时可得到浏览器的请求报文头部信息(
Request Headers
),可用于模拟浏览器的请求操作,以应对网站的安全验证。这里百度图片仅验证User-Agent
2. 代码实现
引入相关库
import re import requests import urllib from time import sleep
定义类并初始化
class GetBaiduImage: def __init__(self, url, path, keyword, page): self.url = url self.path = path self.keyword = keyword self.page = page self.pn = 1 # pn代表从第几张图片开始获取,百度图片下滑时默认一次性显示30张 # self.first = 1 # 代表从第几张图片开始获取,必应图片下滑时默认一次性显示30张
模拟浏览器拉取数据
- 注意:这里的
param
就是刚刚抓包得到链接的参数部分,一个都不能少
def GetHtmlText(self): # 根据传入的url请求网站,并返回得到的数据 param = { 'tn': 'resultjson_com', 'logid': ' 7517080705015306512', 'ipn': 'rj', 'ct': '201326592', 'is': '', 'fp': 'result', 'queryWord': self.keyword, 'cl': '2', 'lm': '-1', 'ie': 'utf-8', 'oe': 'utf-8', 'adpicid': '', 'st': '', 'z': '', 'ic': '', 'hd': '', 'latest': '', 'copyright': '', 'word': self.keyword, 's': '', 'se': '', 'tab': '', 'width': '', 'height': '', 'face': '', 'istype': '', 'qc': '', 'nc': '1', 'fr': '', 'expermode': '', 'force': '', 'cg': 'star', 'pn': self.pn, 'rn': '30', 'gsm': '1e', } # ============================================================================= # # first 参数控制从第 first 张图开始(与百度图片的 pn 参数类似) # param = { # 'q': self.keyword, # 'first': self.first, # 'count': '30', # 'cw': '1177', # 'ch': '593', # 'relp': '35', # 'tsc': 'ImageBasicHover', # 'datsrc': 'I', # 'layout': 'RowBased_Landscape', # 'mmasync': '1', # 'dgState': '', # 'IG': '9D7CBFCB39434C9583EA8186FB044DAA', # 'SFX': '3', # 'iid': 'images.5571' # } # ============================================================================= header = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } try: # 模拟浏览器拉取数据,防止被识别为爬虫并禁掉 html_res = requests.get(self.url, headers=header, params=param) html_res.encoding = html_res.apparent_encoding html_res.raise_for_status() # html_res是响应response,text方法返回字符串形式的html,而content方法是直接拉取得到的字节类型数据,还需要手动解码为字符串 # 当text()显示不正常时使用content.decoding('utf-8/gbk/gbk2312') html_text = html_res.content.decode('utf-8') except: print('网页获取失败:', html_res.status_code) return None return html_text
- 注意:这里的
分析拉取到的html文本(变量
html
)并获取图片的下载链接- 分析发现
thumbURL
对应的就是图片下载链接
def GetImageUrls(self): html = self.GetHtmlText() with open('test.txt', 'w', encoding='utf-8') as f: f.write(html) # urls = re.findall('murl":"(.*?)"', html, re.S) # 必应图片 urls = re.findall('"thumbURL":"(.*?)"', html, re.S) # 百度图片 return urls
- 分析发现
下载图片
def Download(self): name = 1 for n in range(int(page)): img_urls = self.GetImageUrls() for img_url in img_urls: try: urllib.request.urlretrieve(img_url, path+'%s.jpg'%name) except Exception: print("下载失败!") else: sleep(0.5) print('成功下载第%s张图片' % (str(name))) name += 1 # 下载下一页 self.pn += 29 # 百度图片 # self.first += 29 # 必应图片
main函数
if __name__ == '__main__': url = 'https://image.baidu.com/search/acjson?' path = 'E:/学习资料/Python/code/百度图片/' keyword = '风景壁纸' # 想要搜索的图片 page = '2' # 想要爬取几页 imgobj = GetBaiduImage(url, path, keyword, page) imgobj.Download()
3. 缺陷
- 对于爬取必应图片,将上面的注释取消,并注释对应部分即可
- 百度图片这种方法只能爬取缩略图
- 必应图片可爬取高清大图,但每隔几张就会爬取失败。对应代码中“下载失败”的异常处理
4. 源代码
三、爬取必应的每日壁纸
已经有人搜集了必应的每日壁纸:必应每日高清壁纸。并提供了直接下载,因此我们直接从该网站上爬取图片。
1. 分析下载链接
页数
分析发现每一页对应的链接应该是:
https://bing.ioliu.cn/?p={n}
。n对应页数。所以我们可以找到总页数并将其提取出来
图片链接
- 这里直接获取下载链接并存为二进制文件。或者也可以像第一节那样获取图片链接并使用
urllib.request.urlretrieve()
函数下载
- 这里直接获取下载链接并存为二进制文件。或者也可以像第一节那样获取图片链接并使用
获取图片信息,并将之作为图片名
2. 代码实现
引入相关库
import re import requests from bs4 import BeautifulSoup from time import sleep import os
模拟浏览器拉取数据
def GetHtmlText(self, url): # 根据传入的url请求网站,并返回得到的数据 try: # 模拟浏览器拉取数据,防止被识别为爬虫并禁掉 user_agent = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'} html_text = requests.get(url, headers = user_agent) html_text.encoding = html_text.apparent_encoding html_text.raise_for_status() except: print('网页获取失败:', html_text.status_code) return None return html_text
获取最大页数
def GetMaxPageCount(self): # 获取主页信息,并且获取网站的最大页数 soup = BeautifulSoup(self.GetHtmlText(self.url).text, "html.parser") tag_page = soup.find('div', {'class':'page'}) page_txt = None for tag_child in tag_page.children: if(tag_child.name == 'span'): page_txt = tag_child.string match = re.search(r'(?<=1 / )\d*', page_txt) max_page_count = int(match.group(0)) return max_page_count
存储图片
def SavePictureInUrl(self, pic_url, pic_name): # 根据传入的url链接获取图片的二进制数据,并且根据传入的路径和文件名将文件写入到对应的路径中 source = self.GetHtmlText(pic_url) if source == None: return file_name = '{}.jpg'.format(pic_name) try: with open(self.pic_path+file_name, 'wb') as f: f.write(source.content) sleep(0.005) except OSError: print("此图片下载失败!")
下载一页的图片
def GetOnePageJpg(self, page_count): # 从返回的网页数据中获取每张图片的相关信息以及图片下载的url,然后调用相关函数下载图片 url = 'https://bing.ioliu.cn/?p={}'.format(page_count) soup = BeautifulSoup(self.GetHtmlText(url).text, 'html.parser') tag_container = soup.find_all('div', {'class':'container'}) tag_item = tag_container[1] for tag_pic in tag_item.children: # 获取图片的标题和日期信息并且拼接成图片名 tag_title = tag_pic.find('h3') text_title = tag_title.string a = re.findall(r'[^\*"/:?\\|<>]', text_title, re.S) #剔除某些不能作为文件名的特殊字符 text_title = ''.join(a) tag_calendar = tag_pic.find('p', {'class':'calendar'}) tag_em = tag_calendar.find('em') text_calendar = tag_em.string text_pic_name = text_calendar + '__' + text_title if(os.path.exists(self.pic_path+text_pic_name+'.jpg')): continue # 获取图片的下载url tag_download = tag_pic.find('a', {'class':'ctrl download'}) pic_url = self.url + tag_download['href'] #信息保存到图片中 self.SavePictureInUrl(pic_url, text_pic_name) print('.', end='', flush=True) #输出进度信息
下载所有页的图片
def GetAllPageJpg(self): # 爬取所有的图片,并保存在输入的路径参数下 max_page_count = self.GetMaxPageCount() for page_index in range(1, max_page_count): self.GetOnePageJpg(page_index) print('\r', '正在获取,已完成:{:.2f} %'.format(page_index/max_page_count*100), end = '', flush=True) #输出进度信息
main函数
def main(): url = 'https://bing.ioliu.cn/' pic_path = 'E:/学习资料/Python/code/必应每日壁纸/' getBingImg = GetBingImage(url, pic_path) getBingImg.GetAllPageJpg() if __name__ == '__main__': main()
3. 源代码
四、爬取豆瓣评论
1. 分析下载链接
获取完整评论链接。分析发现每篇影评在此链接显示并不完整,要完整链接需要再次跳转。
获取下一页的链接。发现每一页都是由原始链接和
?start={n}
拼接得到。该信息也可匹配得到,由属性next
对应通过完整评论链接获取全文内容及其它相关信息(属性
article
对应内容)- 同时在这儿我们可以获取标题、作者和日期等相关信息。
2. 代码实现
引入相关库
import re import requests from bs4 import BeautifulSoup from time import sleep
模拟浏览器拉取数据
def GetHtmlText(self, url): # 根据传入的url请求网站,并返回得到的数据 try: # 模拟浏览器拉取数据,防止被识别为爬虫并禁掉 user_agent = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'} html_text = requests.get(url, headers = user_agent) html_text.encoding = html_text.apparent_encoding html_text.raise_for_status() except Exception: print('网页获取失败:', html_text.status_code) return None return html_text
获取完整评论链接和下一页链接
def GetNextPageAndCompleUrls(self, url): # 1.获取下一页评论的链接 # 2.获取完整评论的链接 # 获取主页信息,并且获取下一页评论的尾部链接 soup = BeautifulSoup(self.GetHtmlText(url).text, "html.parser") tag_next = soup.find('span', {'class':'next'}) match = re.search('href="(.*?)"', str(tag_next)) next_page = self.url + match.group(0)[6:-1] # 拼接下一页url如"?start=20" # 获取该页所有评论的完整评论链接 tag_main = soup.find_all('div', {'class':'main-bd'}) comple_urls = re.findall('<a href="(.*?)">', str(tag_main)) return next_page, comple_urls
爬取所有评论并存储
def SaveComments(self, url): # 爬取所有评论并储存 count = 1 # 评论条数 for i in range(self.pages): source = self.GetNextPageAndCompleUrls(url) url = source[0] # 下一页的链接 comple_urls = source[1] # 所有的评论链接,是一个大小为20的列表 for comple_url in comple_urls: soup = BeautifulSoup(self.GetHtmlText(comple_url).content.decode('utf-8'), "html.parser") # 获取评论相关信息 tag_article = soup.find('div', {'class':'article'}) # 作者 author = re.search('<span>(.*?)</span>',str(tag_article)).group(0)[6:-7] # 时间 time = re.findall('<span class="main-meta".*?>(.*?)</span>', str(tag_article))[0] # 标题 title = re.findall('<span property="v:summary">(.*?)</span>', str(tag_article))[0] # 正文 content = re.findall('<p data-page="0">(.*?)</p>', str(tag_article), re.S) # 拼接并存储 head = "标题: " + title + '\n' + "作者: " + author + '\n' + "日期: " + time + '\n' content = '\n'.join(content) article = head + content with open(self.path, 'a') as f: f.write('--------------第{}条评论-----------------------\n'.format(count)) f.write(article) f.write('\n\n') print("写入第{}条评论".format(count)) count += 1 sleep(0.5)
main函数
def main(): url = 'https://movie.douban.com/subject/30174085/reviews' path = 'E:/学习资料/Python/code/豆瓣评论/comments.txt' pages = 3 getDoubanComments = GetDoubanComments(url, path, pages) getDoubanComments.SaveComments(url) if __name__ == '__main__': main()
3. 缺陷
- 一些评论无法爬取,应该是网站的反爬机制导致的。最好是利用ip代理解决,可参考:爬虫遇到反爬机制怎么办? 看看我是如何解决的!