2.爬取 deviantart gallery 完整图片
分为两部分:
1. 第一部分使用 python3 + selenium + chrome
来获取 gallery 页面包含的图片页面地址。
2. 第二部分使用 python3 + requests
爬取第一步获取的图片页面内的高清大图。
2.1 selenium 获取图片页面地址
因为 gallery 页面包含的图片页面地址初始只有24个,要获得所有就需要不断下拉触发ajax以加载更多图片页面地址,而这个网站的ajax参数无法解密(在上篇初步爬取中提到过1-初步爬取 deviantart gallery),所以需要使用 selenium 来控制浏览器完成下拉模拟并解析ajax加载后的页面。
程序最后的结果: imgPageUrl
属性,存储一个个包含高清大图页面地址的列表。
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import time
# import requests
import pymongo
import re
db_config = {
'host': 'localhost',
'port': 27017,
'db': 'test_database', # 保存的目的数据库名
'col': 'wlopImgPageUrl', # 保存的目的集合
'auth': True, # 我的mongodb打开了登陆认证, 若没有认证: 此参数修改为 False
'user': 'testuser', # 自己的认证用户名
'passwd': 'testpass' # 自己的认证用户密码
}
class simChrome():
def __init__(self, artist='wlop'):
'''
:param artist: deviantart艺术家 ID。
'''
self.base_url = 'https://www.deviantart.com/{}/gallery/'.format(artist)
def initChrome(self):
Chrome_setting = {"profile.managed_default_content_settings.images": 2}
chrome_option = webdriver.ChromeOptions()
chrome_option.add_experimental_option("prefs", Chrome_setting)
chrome_option.add_argument('--headless')
self.browser = webdriver.Chrome(chrome_options=chrome_option)
self.wait = WebDriverWait(self.browser, 10)
def initMongo(self, **kw):
'''
:param kw: MongoDB的参数。
'''
client = pymongo.MongoClient(host=kw['host'], port=kw['port'])
if kw['auth'] == True:
db_auth = client.admin
db_auth.authenticate(kw['user'], kw['passwd'])
db = client[kw['db']]
self.collection = db[kw['col']]
def start(self, **kw):
'''
:param kw: MongoDB的参数。
'''
try:
self.initChrome()
self.initMongo(**kw)
except Exception as err:
print(err)
def indexPage(self):
try:
self.browser.get(self.base_url)
self.wait.until(EC.presence_of_element_located((By.ID, 'gmi-')))
print('Dom successfully loaded!')
except TimeoutException:
print('Selenium timeout, try again.')
self.indexPage() # 如果超时那就重新再来一遍
def simulateScroll(self, scroll_step=3000, max_height=None, fixed_times=3):
'''
本程序判断是否爬取所有的依据是比较动作前后 set 的长度,如果三次动作后长度依然没有
变化则判断没有已经新的数据
:param scroll_step: 每次下拉的长度,和浏览器窗口大小有关系,需要视实际而定。
暂时没有想到科学确定的方法。
:param max_height: 最大距离。如果设置为None,则爬取所有。
:param fixed_times: fixed_times次动作后长度依然没有变化,判定为结束。有时获取
不到完整的时可适当增加此次数。
'''
# 获取所有图片页面地址的网页元素
element = set()
gallery_torpedo = self.browser.find_element_by_id('gmi-')
height = 0
no_change = 0 # 对动作后长度没有变化的次数统计
present_len = 0 # 当前集合的长度
while True:
previous_len = present_len # 动作前集合的长度
self.browser.execute_script(
'window.scrollTo(0, {})'.format(height))
time.sleep(0.5) # 等待浏览器解析并加载到页面
items = gallery_torpedo.find_elements_by_css_selector(
'span[class~=thumb]>a')
for item in items:
# 从网页元素中提取url,保存到 set 里
element.update((item.get_attribute('href'),))
present_len = len(element)
if previous_len == present_len:
no_change += 1
else:
no_change = 0
if no_change == fixed_times or height == max_height:
# fixed_times次动作后长度依然没有变化或者到达设定的最大值,则停止
break
height += scroll_step
self.browser.close()
self.imgPageUrl = list(element) # set 转 list
def saveMongoDB(self):
for item in self.imgPageUrl:
result = {
'page_name': re.findall('.*/(.*)-\d', item, re.S)[0],
'url': item
}
try:
if self.collection.insert_one(result):
print('{} save to MongoDB successful!'.format(result['page_name']))
except Exception as err:
print(err)
simulator = simChrome()
simulator.start(**db_config)
simulator.indexPage()
simulator.simulateScroll()
simulator.saveMongoDB()
url_list = simulator.imgPageUrl
# 列表,存储了有高清大图的页面的地址。
print('len(url_list): ', len(url_list), url_list[:3])
len(url_list): 154
['https://www.deviantart.com/wlop/art/Sarlia-515699512', 'https://www.deviantart.com/wlop/art/The-Lonely-Parade-3d-716468598', 'https://www.deviantart.com/wlop/art/Lean-653039904']
2.2 requests 获取图片页面内高清图
多进程池的一个问题:
由于 Windows 系统不支持 forking,spyder IDE 重定向了 stdout。所以在这两情况下多进程的内部无法 print,所以下面都使用 logging 来保存日志。详细问题见 Simple Python Multiprocessing function in Spyder doesn’t output results。
访问太多,最好使用代理来爬。但是目前我还不会>_<。
提示:程序运行后由于网络原因,速度会非常慢。我使用校园网半小时才爬完 wlop大大,一共有154张图片。
如果下面程序报错: TypeError: object of type 'NoneType' has no len()
,原因是网络问题,使 get_page()
返回了 None
。在log 文件里查看详细原因。
BUG:
logging 无法获取所有日志。我爬完 154 张图片,应该至少有 154*2 个日志才对,但是我这次爬完只有 60 行日志。
import re
import requests
from requests.exceptions import RequestException
from bs4 import BeautifulSoup
import os
import pymongo
from multiprocessing import Pool
import logging
logging.basicConfig(level=logging.INFO,
filename='./log.txt',
filemode='w',
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
client = pymongo.MongoClient(host='localhost', port=27017)
db_auth = client.admin
db_auth.authenticate('testuser', 'testpass')
db = client['test_database']
collection = db['wlopImgUrl']
def get_page(url: str) -> str:
'''requests获取网页源码'''
# 设置浏览器标识user-agent
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36'
}
try:
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200:
return response.text
return None
except requests.exceptions.Timeout:
logging.info('Requests timeout try again')
get_page(url)
except RequestException as e:
logging.error(e)
return None
def parse_page(text: str) -> dict:
'''解析图片地址'''
soup = BeautifulSoup(text, 'html5lib')
item = soup.select('img.dev-content-full')[0]
return item.attrs['src']
def download_img(result: dict):
r = requests.get(result['img_url'], stream=True) # stream loading
file_name = './temp/wlop/{}.jpg'.format(result['img_name'])
with open(file_name, 'wb') as f:
for chunk in r.iter_content(chunk_size=32):
f.write(chunk)
logging.info('%s download successful!'.format(result['img_name']))
def save_to_db(result):
try:
if collection.insert_one(result):
logging.info('{} save to MongoDB successful!'.format(result['img_name']))
except Exception as err:
logging.error(err)
def main(url):
html = get_page(url)
img_url = parse_page(html)
result = {
'img_name': re.findall('.*/(.*?)-', img_url, re.S)[0],
'img_url': img_url
}
save_to_db(result)
download_img(result)
if __name__ == '__main__':
os.makedirs('./temp/wlop/', exist_ok=True)
pool=Pool()
pool.map(main, url_list)
print('finished')
3. 高效爬取ajax的想法
理想环境:python3 + selenium + chrome + browsermob-proxy
deviantart gallery每次下拉到底部后ajax请求返回包含 24 张图片的 json 数据,浏览器需要把这些数据解析渲染到页面上后,才能获得对应新的元素,这样导致速度非常慢。
所以我想能不能把这些请求返回的json数据给保存下来,自己直接拿来解析图片真实url,这样省去了页面解析并渲染的过程,然后我就查到了这样一个工具 browsermob-proxy 以及Python的库文件 browsermobproxy
,它可以获取浏览器的所有请求以及回应,近期事情太多没有时间和精力搞。
评论