python爬虫之动态网页爬取——poco爬虫

背景

做这个一个爬虫源于项目中需要做ai模型训练需要用到大量的同一人的不同场景下的照片,正好这种摄影网站里面可以找到。

项目初始化

我们先创建一个爬虫项目,这里我们用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()
posted @ 2023-04-03 22:49  loveletters  阅读(154)  评论(1编辑  收藏  举报