Python爬虫抓取js添加到网页的图片

692次阅读
没有评论

采用PyQt5+urllib3+BeautifulSoup4进行动态网页的图片抓取爬虫

    心血来潮,想在某个网站抓取图片,结果用requests下载下来,却发现,里面不含有图片,也就是说,图片全是由js等代码放到页面上去的,所以,requests下载的页面并没有执行js内容,于是就不能从里面找到所要的图片。为了解决这个问题,聪明的网友们发现用PyQt的网页相关模块可以解决这个问题,而不需要用selenium等模块操作浏览器,于是乎,参考了一些文章,终于达到了我的目的。于是在这里记录相关步骤,也可给各位一个参考。

本文基于以下环境和版本:
    Windows 7
    Python3.6.0 x32
    Python模块:
        beautifulsoup4==4.6.0
        urllib3==1.22
        PyQt5==5.10.1
        注:PyQt5.8.2也测试通过
        IDE:PyCharm3.2

首先,准备好测试用的HTML等文件
main.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Main Page</title> </head> <body> <div>这是主页</div> <script type="text/javascript" src="./main.js"></script> </body> </html>

main.js

function dynamicImg(img_url) { var div = document.createElement("div"); div.innerText = img_url;

var img = document.createElement("img"); img.src = img_url; img.classList.add("test"); document.body.appendChild(div); document.body.appendChild(img); } // 用js在页面上添加两张图片 dynamicImg("./test1.jpg"); dynamicImg("./test2.png");

图片请自己准备。将html,js,jpg等文件放到某个文件夹下,比如我的存放位置看下图浏览器地址栏。
本地用浏览器打开之后,效果如下(图片较大,截了一部分):
Python爬虫抓取js添加到网页的图片
以下代码均在文件app.py中
以下代码用到的库:

import urllib.request import urllib.response import random import bs4 as bs import os import sys from PyQt5.QtWebEngineWidgets import QWebEnginePage from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QUrl

如果用request直接下载的效果:

def useRequestMethod(url): """ 传统方法不能下载动态网页 """ response = urllib.request.urlopen(url) with open("download.html", "w+", encoding="utf-8") as f: f.write(response.read().decode("utf-8"))

if __name__ == '__main__': url = "file:///E:/Code/Python/SimpleCrawler/CrawlerImageExample/main.html" useRequestMethod(url)

上述代码将下载的HTML存入了download.html中,打开看一下:
Python爬虫抓取js添加到网页的图片
可以看到,下载下来的HTML并没有执行JS代码,导致HTML中根本不含图片链接,于是这个时候,就该PyQt5出场了。

使用PyQt5下载js执行过后的动态页面

这里封装了一个类,用PyQt5的QWebEnginePage去加载url并等url完全执行完毕后,得到这个url的整个HTML

class MyWebBrowser(QWebEnginePage): app = None # 类变量 QApplication # 实际测试时,若调用了多个MyWebBrowser对象(有先后顺序的调用) # 比如现在某些页面上,获取了所有包含图片的页面链接,再去打开这些链接上抓取图片 # 容易在这一步 super().__init__() 异常崩溃 # 可能是在 QApplication.quit()调用时,出现了资源释放异常 # 改成类变量控制后,没有出现崩溃现象,这个还需要再测试测试

def __init__(self): if MyWebBrowser.app is None: MyWebBrowser.app = QApplication(sys.argv) # self.app = QApplication(sys.argv) # print("DownloadDynamicPage") super().__init__() self.html = '' # 将加载完成信号与槽连接 self.loadFinished.connect(self._on_load_finished) # print("DownloadDynamicPage Init")

def downloadHtml(self, url): """ 将url传入,下载此url的完整HTML内容(包含js执行之后的内容) 貌似5.10.1自带一个download函数 这个在5.8.2上也是测试通过的 :param url: :return: html """ self.load(QUrl(url)) print("\nDownloadDynamicPage", url) # self.app.exec_() # 函数会阻塞在这,直到网络加载完成,调用了quit()方法,然后就返回html MyWebBrowser.app.exec_() return self.html

def _on_load_finished(self): """ 加载完成信号的槽 :return: """ self.html = self.toHtml(self.Callable)

def Callable(self, html_str): """ 回调函数 :param html_str: :return: """ self.html = html_str MyWebBrowser.app.quit() # self.app.quit()

使用QWebEnginePage加载效果:

def useWebEngineMethod(url): """ 使用PyQt5的网页组件下载完整的动态网页 """

webBrowser = MyWebBrowser() html = webBrowser.downloadHtml(url) with open("download_by_web_engine.html", "w+", encoding="utf-8") as f: f.write(html) return html

if __name__ == '__main__': # main() url = "file:///E:/Code/Python/SimpleCrawler/CrawlerImageExample/main.html" # useRequestMethod(url) useWebEngineMethod(url)

打开存储的download_by_web_engine.html文件,如下:
Python爬虫抓取js添加到网页的图片
可以看到,里面包含了用js添加的div和img等元素

然后,既然都获取到包含图片链接的网页了,接下来不就可以用BeautifulSoup为所欲为(笑)了。
接下来是:

解析这个页面上的img标签,获取url

def getImgUrlList(html: str): """ 从网页中解析所需要的图片的url,存储进list中 """ # 使用html.parser解析 soup = bs.BeautifulSoup(html, 'html.parser') # 按条件查找img标签 pageOptionList = soup.find_all('img', class_='test') print(pageOptionList) imgUrlList = list() for pageOptionEle in pageOptionList: # 获取img标签的src中的url imgUrl = pageOptionEle.get("src", None) if imgUrl is None: continue imgUrlList.append(imgUrl) return imgUrlList

if __name__ == '__main__': # main() url = "file:///E:/Code/Python/SimpleCrawler/CrawlerImageExample/main.html" # useRequestMethod(url) html = useWebEngineMethod(url) imgUrlList = getImgUrlList(html) print(imgUrlList)

打印结果:
Python爬虫抓取js添加到网页的图片

接下来,

用urllib下载图片

由于有的网站可能会采取一定的防爬虫措施,导致出现403错误,所以可以使用不同的User-Agent来尽量减少被发现的概率。下面这张图是Chrome浏览器的User-Agent:
(按下F12,选择Network,F5重载,左侧选择一个图片(最好是想要爬取的图片)或者其他文件,右侧选择Headers,即可滚动查看到Request Headers中各项内容,除User-Agent外,还有Referer信息,最好也填入urllib中)
Python爬虫抓取js添加到网页的图片
然后就可封装成一个专门用于下载的类:

class DownloadUrl: """ 使用urllib下载文件 """ # 用于绕过403错误的不同的浏览器标识 userAgentList = [ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36" ]

@staticmethod def download(url, referer_url: str = ""): ''' 下载url所指文件,可一定程度上绕过403错误 ''' randomUserAgent = random.choice(DownloadUrl.userAgentList)

request = urllib.request.Request(url)

request.add_header("User-Agent", randomUserAgent) if "http" in url: host = url.split(r'/')[2] else: host = url.split(r'/')[0] # 请自行测试分割host方案,host最好也填写(别的博客中看到了填写此值,不太清楚是否真的有效) request.add_header("Host", host) request.add_header("GET", url) # referer_url 也最好填写,一般是本页或者Host,具体填入什么请根据被爬取的页面中Chrome浏览器Request Headers中的Referer值填入 # 例如本博客的Referer为:https://blog.csdn.net/and_zj/article/details/80003543 if referer_url != "": request.add_header("Referer", referer_url) response2 = urllib.request.urlopen(request) print(response2.getcode(), url) # 返回下载的所有内容 return response2.read()

然后编写从之前的imgUrlList中获取imgUrl,再调用DownloadUrl.download()下载到文件,再写入文件即可。

def downloadImage(imgUrlList: list, saveDir: str): """ 下载imgUrlList中所有的imgUrl,请注意,要组合完整的url """ if not os.path.exists(saveDir): os.makedirs(saveDir) index = 0 for imgUrl in imgUrlList: index = index + 1 # 由于imgUrl可能不是完整的Url,所以需要组装成完整可访问的Url # 请根据实际情况,自己设计组装方案,或者,在上一步获取是,就组装好传递过来 imgUrl = url.rsplit(r'/', 1)[0] + "/" + imgUrl.rsplit(r'/', 1)[1] # print(imgUrl) try: # referer_url 最好填写,参考Chrome中的referer结果填写 # 这里是本地网页,所以没填 content = DownloadUrl.download(imgUrl) except Exception as e: print(e, imgUrl) continue #这里关于403错误的问题,之前误认为不会引发Exception,其实是会的,故去掉这里 #if b'403' in content: # # 403并不会引起上述Exception # print("403", imgUrl) # continue # 对下载文件重命名:路径 + 序号 + 原文件名后缀 file_name = saveDir + str(index) + '.' + imgUrl.split(r'.')[-1] # 图片文件以二进制格式写入 f = open(file_name, 'wb') f.write(content) f.close()

运行代码改为:

if __name__ == '__main__': url = "file:///E:/Code/Python/SimpleCrawler/CrawlerImageExample/main.html" # useRequestMethod(url) html = useWebEngineMethod(url) imgUrlList = getImgUrlList(html) print(imgUrlList) downloadImage(imgUrlList,"./downloadImage/")

运行之后,确实下载下来了,如下图所示:
Python爬虫抓取js添加到网页的图片

以下是完整代码:

已测试通过

# -*— coding:utf-8 -*- import urllib.request import urllib.response import random import bs4 as bs import os import sys from PyQt5.QtWebEngineWidgets import QWebEnginePage from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QUrl

class MyWebBrowser(QWebEnginePage): app = None # 类变量 QApplication # 实际测试时,若调用了多个MyWebBrowser对象(有先后顺序的调用) # 比如现在某些页面上,获取了所有包含图片的页面链接,再去打开这些链接上抓取图片 # 容易在这一步 super().__init__() 异常崩溃 # 可能是在 QApplication.quit()调用时,出现了资源释放异常 # 改成类变量控制后,没有出现崩溃现象,这个还需要再测试测试

def __init__(self): if MyWebBrowser.app is None: MyWebBrowser.app = QApplication(sys.argv) # self.app = QApplication(sys.argv) # print("DownloadDynamicPage") super().__init__() self.html = '' # 将加载完成信号与槽连接 self.loadFinished.connect(self._on_load_finished) # print("DownloadDynamicPage Init")

def downloadHtml(self, url): """ 将url传入,下载此url的完整HTML内容(包含js执行之后的内容) 貌似5.10.1自带一个download函数 这个在5.8.2上也是测试通过的 :param url: :return: html """ self.load(QUrl(url)) print("\nDownloadDynamicPage", url) # self.app.exec_() # 函数会阻塞在这,直到网络加载完成,调用了quit()方法,然后就返回html MyWebBrowser.app.exec_() return self.html

def _on_load_finished(self): """ 加载完成信号的槽 :return: """ self.html = self.toHtml(self.Callable)

def Callable(self, html_str): """ 回调函数 :param html_str: :return: """ self.html = html_str MyWebBrowser.app.quit() # self.app.quit()

def useRequestMethod(url): """ 传统方法不能下载动态网页 """ response = urllib.request.urlopen(url) with open("download.html", "w+", encoding="utf-8") as f: f.write(response.read().decode("utf-8"))

def useWebEngineMethod(url): """ 使用PyQt5的网页组件下载完整的动态网页 """

webBrowser = MyWebBrowser() html = webBrowser.downloadHtml(url) with open("download_by_web_engine.html", "w+", encoding="utf-8") as f: f.write(html) return html

def getImgUrlList(html: str): """ 从网页中解析所需要的图片的url,存储进list中 """ # 使用html.parser解析 soup = bs.BeautifulSoup(html, 'html.parser') # 按条件查找img标签 pageOptionList = soup.find_all('img', class_='test') print(pageOptionList) imgUrlList = list() for pageOptionEle in pageOptionList: # 获取img标签的src中的url imgUrl = pageOptionEle.get("src", None) if imgUrl is None: continue imgUrlList.append(imgUrl) return imgUrlList

class DownloadUrl: """ 使用urllib下载文件 """ # 用于绕过403错误的不同的浏览器标识 userAgentList = [ "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36" ]

@staticmethod def download(url, referer_url: str = ""): ''' 下载url所指文件,可一定程度上绕过403错误 ''' randomUserAgent = random.choice(DownloadUrl.userAgentList)

request = urllib.request.Request(url)

request.add_header("User-Agent", randomUserAgent) if "http" in url: host = url.split(r'/')[2] else: host = url.split(r'/')[0] # 请自行测试分割host方案,host最好也填写(别的博客中看到了填写此值,不太清楚是否真的有效) request.add_header("Host", host) request.add_header("GET", url) # referer_url 也最好填写,一般是本页或者Host,具体填入什么请根据被爬取的页面中Chrome浏览器Request Headers中的Referer值填入 # 例如本博客的Referer为:https://blog.csdn.net/and_zj/article/details/80003543 if referer_url != "": request.add_header("Referer", referer_url) response2 = urllib.request.urlopen(request) print(response2.getcode(), url) # 返回下载的所有内容 return response2.read()

def downloadImage(imgUrlList: list, saveDir: str): """ 下载imgUrlList中所有的imgUrl,请注意,要组合完整的url """ if not os.path.exists(saveDir): os.makedirs(saveDir) index = 0 for imgUrl in imgUrlList: index = index + 1 # 由于imgUrl可能不是完整的Url,所以需要组装成完整可访问的Url # 请根据实际情况,自己设计组装方案,或者,在上一步获取是,就组装好传递过来 imgUrl = url.rsplit(r'/', 1)[0] + "/" + imgUrl.rsplit(r'/', 1)[1] # print(imgUrl) try: # referer_url 最好填写,参考Chrome中的referer结果填写 # 这里是本地网页,所以没填 content = DownloadUrl.download(imgUrl) except Exception as e: print(e, imgUrl) continue #这里关于403错误的问题,之前误认为不会引发Exception,其实是会的,故去掉这里 #if b'403' in content: # # 403并不会引起上述Exception # print("403", imgUrl) # continue # 对下载文件重命名:路径 + 序号 + 原文件名后缀 file_name = saveDir + str(index) + '.' + imgUrl.split(r'.')[-1] # 图片文件以二进制格式写入 f = open(file_name, 'wb') f.write(content) f.close()

if __name__ == '__main__': url = "file:///E:/Code/Python/SimpleCrawler/CrawlerImageExample/main.html" # useRequestMethod(url) html = useWebEngineMethod(url) imgUrlList = getImgUrlList(html) print(imgUrlList) downloadImage(imgUrlList, "./downloadImage/")

以上就是本次的开发历程了,看懂之后,稍加改进就可以对自己想爬取得网页开爬了,再加个数据库什么,自动爬取没爬过的网页,没下载的图片什么的,美滋滋。

参考资源

  • BeautifulSoup4===4.4.0文档:
    https://www.crummy.com/software/BeautifulSoup/bs4/doc/
  • urllib3==1.22.0文档:
    http://urllib3.readthedocs.io/en/latest/
  • PyQt5==5.10.1文档:
    http://pyqt.sourceforge.net/Docs/PyQt5/
    http://zetcode.com/gui/pyqt5/
  • 博客文章
    http://www.showerlee.com/archives/2109
    https://blog.csdn.net/jsqfengbao/article/details/44594985
    https://www.cnblogs.com/zhaof/p/6910871.html
  • 最后,感谢爱分享的各位大佬们

    神龙|纯净稳定代理IP免费测试>>>>>>>>天启|企业级代理IP免费测试>>>>>>>>IPIPGO|全球住宅代理IP免费测试

    相关文章:

    版权声明:Python教程2022-10-28发表,共计11387字。
    新手QQ群:570568346,欢迎进群讨论 Python51学习