python爬虫之动态网页爬取——poco爬虫
我们先创建一个爬虫项目,这里我们用scrapy框架来创建。
scrapy startproject poco
然后cd 到 poco文件夹中初始化一下项目
scrapy genspider pocoSpider poco.com
打开项目,项目目录结构如下
我们的爬虫代码就写在pocoSpider文件中,现在我们打开网站分析一下网页。
我们选择人像分类来爬取
https://www.poco.cn/works/works_list?classify_type=1&works_type=medal
可以看到页面是有很多用户id,我们要先拿到每个id的url再进去详情页抓取图片。
右键查看网页源代码,发现页面是有js动态生成的网页
而且页面是懒加载的,右键审查元素,查看网络
找到请求图片的请,拿到请求参数,url地址,复制到postman里面调用一下,成功拿到返回数据
分析请求参数,猜想是否能够通过,修改参数来获得响应结果,但是修改请求参数后,请求失败,看来直接修改参数的方式不能拿到我们需要的url地址。
我们随意点进去个查看网页源代码,看看详情也是否也是js动态加载的页面。
很幸运,可以在源代码里面直接查看到我们需要的数据,每张图片的地址就在 标签里面,那就简单了。我们可以先爬取这个页面的图片
import scrapy
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
class PocospiderSpider(scrapy.Spider):
name = 'pocoSpider'
allowed_domains = ['poco.com']
start_urls = ['https://www.poco.cn/works/detail?works_id=20992476']
def parse(self, response):
img_list = response.xpath("//img/@data-src").extract()
for img in img_list:
print(img)
if __name__ == "__main__":
process = CrawlerProcess(get_project_settings())
process.crawl('pocoSpider')
process.start()
如设想的一样,我们成功拿到了当前id里面的所有图片:
我们现在只需要拿到所有id对应的url地址再循环请求就能拿到所有图片的地址了
既然通过修改参数的方式不能够拿到数据,那我们就用selenium框架来模拟浏览器操作。
分析页面,当每次下拉到最后发送请求。我们关闭页面的图片加载,以便加速访问.我们把爬取到的数据存储到数据库中,这里我们用mongod。定义下拉刷新的函数,execute_times 每次刷新后睡眠0.5秒以便浏览器渲染页面。我们这里刷新40次来获得数据。
我们在setting.py文件中设置好数据库的地址,端口,数据库名称
LOCAL_MONGO_HOST = '127.0.0.1'
LOCAL_MONGO_PORT = 27017
DB_NAME = 'POCO'
写好selenium模拟操作的代码
from pymongo.errors import DuplicateKeyError
from selenium import webdriver
import time
from lxml import etree
import pymongo
from poco.settings import LOCAL_MONGO_HOST,LOCAL_MONGO_PORT,DB_NAME
chrome_opt = webdriver.ChromeOptions()
prefs = {'profile.managed_default_content_settings.images': 2}
chrome_opt.add_experimental_option('prefs',prefs)
driver = webdriver.Chrome(chrome_options=chrome_opt)
driver.get("https://www.poco.cn/works/works_list?classify_type=1&works_type=editor")
time.sleep(0.5)
def execute_times(times):
for i in range(times + 1):
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(0.5)
execute_times(40)
html = driver.page_source
xpath_html = etree.HTML(html)
a_list = xpath_html.xpath("//div[@class='vw_works_list']/a/@href")
print(len(a_list))
mongo_client = pymongo.MongoClient(LOCAL_MONGO_HOST, LOCAL_MONGO_PORT)
collection = mongo_client[DB_NAME]["idlist"]
for a in a_list:
id = a.split("?")[-1].split("=")[-1]
try:
collection.insert_one({"_id":id,"url":a})
except DuplicateKeyError as e:
collection.find_one_and_update({"_id":id},{"$set":{"url":a}})
driver.close()
执行完毕我们大概能拿到800多条数据,我们现在重新来编写爬虫代码。
import pymongo
import scrapy
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
from poco.settings import LOCAL_MONGO_HOST,LOCAL_MONGO_PORT,DB_NAME
class PocospiderSpider(scrapy.Spider):
name = 'pocoSpider'
mongo_client = pymongo.MongoClient(LOCAL_MONGO_HOST, LOCAL_MONGO_PORT)
collection = mongo_client[DB_NAME]["idlist"]
id_list = collection.find()
def start_requests(self):
for id in self.id_list:
item = {}
item["folder"]=id["_id"]
item["img_urls"]=[]
yield scrapy.Request(url=id["url"],callback=self.parse_detail,meta={"item":item})
def parse_detail(self, response):
item = response.meta["item"]
img_list = response.xpath("//img/@data-src").extract()
for img in img_list:
if img == "":
continue
img = "https:"+img
item["img_urls"].append(img)
yield item
if __name__ == "__main__":
process = CrawlerProcess(get_project_settings())
process.crawl('pocoSpider')
process.start()
我们scrapy的pipelines来下载图片,我们先写一个pipeline方法,将图片分id来进行存储,我们重写file_path方法来修改图片的存储路径
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline
from scrapy import Request
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item['img_urls']:
referer = image_url
yield Request(image_url,meta={'item':item,'referer':referer})
def file_path(self, request, response=None, info=None):
item = request.meta['item']
folder = item['folder'].strip()
img_name = request.url.split("/")[-1]
filename = u'img/{0}/{1}'.format(folder,img_name)
return filename
def item_completed(self, results, item, info):
image_path = [x['path'] for ok,x in results if ok]
if not image_path:
raise DropItem('Item contains no images')
# item['image_paths'] = image_path
return item
最后我们在setting文件中修改图片的下载中间件,以及PipeLines
DOWNLOADER_MIDDLEWARES = {
'poco.middlewares.PocoDownloaderMiddleware': 543,
}
IMAGES_STORE=r'E:\\'
IMAGES_EXPIRES = 30
# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
ITEM_PIPELINES = {
'poco.pipelines.MyImagesPipeline': 300,
}
CONCURRENT_REQUESTS = 32
DOWNLOAD_DELAY = 0.1
# Configure item pipeli
至此poco网站的爬虫已经编写完毕,运行代码,已经成功爬取图片,并且按照id来分类存储
后记:其实爬虫代码中还存在一个bug,当爬虫运行10分钟并且id还未爬取完毕的情况下会自动停止,仔细检查代码,发现了一个mongodb的坑。那就是
collection = mongo_client[DB_NAME]["idlist"]
id_list = collection.find()
db.collection.find() 的时候,它返回的不是所有的数据,而实际上是一个“cursor”。它的默认行为是:第一次向数据库查询 101 个文档,或 1 MB 的文档,取决于哪个条件先满足;之后每次 cursor 中的文档用尽后,查询 4 MB 的文档。另外,find() 的默认行为是返回一个 10 分钟无操作后超时的 cursor。如果10分钟没有处理完请求,那我们也就拿不到剩余的url,所以爬虫也就停止了。
解决办法也很简单,那就是最开始的时候直接将id_list中的数据存到列表中。
# -*- coding: utf-8 -*-
import pymongo
import scrapy
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
from poco.settings import LOCAL_MONGO_HOST,LOCAL_MONGO_PORT,DB_NAME
class PocospiderSpider(scrapy.Spider):
name = 'pocoSpider'
mongo_client = pymongo.MongoClient(LOCAL_MONGO_HOST, LOCAL_MONGO_PORT)
collection = mongo_client[DB_NAME]["idlist"]
id_list = collection.find()
ids=[]
for id in id_list:
ids.append(id)
def start_requests(self):
for id in self.ids:
item = {}
item["folder"]=id["_id"]
item["img_urls"]=[]
yield scrapy.Request(url=id["url"],callback=self.parse_detail,meta={"item":item})
def parse_detail(self, response):
item = response.meta["item"]
img_list = response.xpath("//img/@data-src").extract()
for img in img_list:
if img == "":
continue
img = "https:"+img
item["img_urls"].append(img)
yield item
if __name__ == "__main__":
process = CrawlerProcess(get_project_settings())
process.crawl('pocoSpider')
process.start()
上一篇: 函数的参数类型
下一篇: LCT学习笔记(不断更新ing)