Just walk the filesystem tree top-down with os.scandir() and don't follow symlinks.
    :param top: path to scan
    :param skip_dirs: List of dir names to skip
        e.g.: "__pycache__", "temp", "tmp"
    :param on_skip: function that will be called if 'skip_dirs' match.
        e.g.:
        def on_skip(entry, pattern):
            log.error("Skip pattern %r hit: %s" % (pattern, entry.path))
    :return: yields os.DirEntry() instances

def scandir_walk(top, skip_dirs=(), on_skip=None):
    """
    Just walk the filesystem tree top-down with os.scandir() and don't follow symlinks.
    :param top: path to scan
    :param skip_dirs: List of dir names to skip
        e.g.: "__pycache__", "temp", "tmp"
    :param on_skip: function that will be called if 'skip_dirs' match.
        e.g.:
        def on_skip(entry, pattern):
            log.error("Skip pattern %r hit: %s" % (pattern, entry.path))
    :return: yields os.DirEntry() instances
    """
    # We may not have read permission for top, in which case we can't
    # get a list of the files the directory contains.  os.walk
    # always suppressed the exception then, rather than blow up for a
    # minor reason when (say) a thousand readable directories are still
    # left to visit.  That logic is copied here.
    try:
        scandir_it = Path2(top).scandir()
    except PermissionError as err:
        log.error("scandir error: %s" % err)
        return

    for entry in scandir_it:
        if entry.is_dir(follow_symlinks=False):
            if entry.name in skip_dirs:
                on_skip(entry, entry.name)
            else:
                yield from scandir_walk(entry.path, skip_dirs, on_skip)
        else:
            yield entry