В 2013 году на Randomwire была опубликована классная статья. В ней автор указал на интересные особенности японского дизайна. Японцы известны своим минимализмом, но в случае сайтов у них, почему-то, всё наоборот. Страницы пестрят всевозможными цветами, что уже нарушает 3 принципа дизайна, плюс на них используются мелкие иконки и мно-о-о-о-го текста. Да вы и сами видите пример этого буйства фантазии на скриншотах выше, сделанных в ноябре 2022 года.

В упомянутой статье приводилось несколько возможных объяснений, которые позднее были подкреплены исследователями культуры, другими дизайнерами и недовольными гражданами.

И вот мне стало интересно, как обстоят дела сейчас, и можно ли как-то количественно оценить дизайн японских сайтов? Собственно, этим я и занялась...

Что я выяснила

Прогнав 2 671 скриншот самых популярных сайтов из множества стран через ИИ, я смогла сгруппировать паттерны дизайна, используемые в разных частях мира, по их сходству.

Получились отчётливые кластеры, включающие одни и те же всемирно популярные сайты (Google, Twitter, Facebook, Wikipedia и прочие), а также типичные веб-функции (страницы капчи и всплывающие уведомления о политике использования куки).

Однако, если внимательно рассмотреть отдельные сегменты полученной картины, то мы увидим явный паттерн.

При движении снизу вверх страницы становятся всё темнее.

А при движении по горизонтали (скорее, по диагонали) происходит плавный переход от полупустых страниц до загруженных текстом и затем изображениями.

Интересно то, что авторы популярных японских сайтов стремятся избегать в дизайне пустых пространств и тёмных тонов.

И это отличает их от создателей сайтов в других странах, где используются всевозможные виды дизайна.

То есть японцы относительно уникальны в том, что делают акцент на светлом и плотном дизайне страниц.

И здесь возникает логичный вопрос: «Почему?»

Почему японский дизайн иной?

В оригинальной статье автор приводит много возможных причин, включая особенности городского ландшафта, языковой барьер и предпочтения японских пользователей, которые стараются избегать рисков.

Я объединила эти доводы с доводами своих собеседников в Twitter и идеями других интернет-пользователей из Азии, в результате чего выделила три основных причины.

Письменность

Японская письменность имеет много общих логографических символов с корейской и китайской (собирательно именуются CJK). Таких символов много, но дизайнеры не особо горят желанием создавать для них шрифты. Поэтому в распоряжении японских оформителей веба вариантов шрифтов не так много. К тому же, они не могут использовать заглавные символы для создания визуальной иерархии, а увеличенный размер шрифтов ещё и означает более длительную загрузку.

Хотя в других регионах, где используются символы CJK, японский паттерн менее распространён. Ситуация похожа, но здесь дизайнеры больше приемлют тёмные тона и всё же допускают «минималистичные» схемы.

Особенности культуры

Считается, что японцы очень не любят риски, и поэтому им требуется предоставлять много информации по всевозможным вариантам товаров. На формат подачи этой информации может влиять особенность их городской среды, где яркие цветастые огни и компактные пространства являются нормой.

Однако, если сравнивать Японию с её соседями, которые по логике могут иметь культурные сходства, то закономерности не прослеживаются.

Если сгруппировать страны по социоэкономическим и политическим характеристикам (глобальный Север и глобальный Юг), то никаких паттернов мы не увидим.

Технология

Чтобы понять этот аргумент, нужно сначала разобраться, как программное и аппаратное обеспечение привело к современному облику сайтов. Вот интересный блог, где описывается эпоха раннего интернета, а вот ещё один, посвящённый эволюции популярных сайтов.

Коротко это всё можно расписать так:

  • Интернет создавался как инструмент обмена документами между компьютерами.

  • Обмен документами быстро расширился до интерактивного взаимодействия и мультимедиа.

  • Все эти инновации происходили быстро и почти без стандартизации, что привело к появлению множества уникальных вариантов веб-дизайна (которые часто падали).

  • В конечном итоге из-за огромного числа сайтов сёрфинг интернета стал ужасно нестабильным и пользоваться им было крайне сложно.

  • Последующая разработка веб-стандартов и поисковых движков привела к сокращению количества вариантов дизайна.

  • Потом появились смартфоны. Тогда ввиду их небольших экранов и ради экономии трафика на страницах стали размещать меньше контента, что привело к развитию минималистичного дизайна.

И смысл в том, что японский веб-дизайн пропустил последний этап или просто отстал от остального мира. Объясняется это тремя причинами.

Япония стара

Японцы — это одна из самых «старых» наций в мире в том смысле, что средний возраст их граждан выше, чем во многих других странах. Эти люди пользуются интернетом иначе — они придерживаются своих привычек, которым ближе именно старые модели веб-среды (источник).

Сравнение возрастных распределений населения Японии и США
Сравнение возрастных распределений населения Японии и США

Япония медленно перенимает новые технологии

Ситуация усугубляется свойственной японцам неохотой обновлять ПО. Этот довод подкрепляется тем фактом, что на момент объявления Microsoft о завершении поддержки Internet Explorer этим браузером пользовалась половина компаний Японии (источник). А также утверждением Отца японского интернета, Мурай Юна, который отметил активное сопротивление этой технологии со стороны населения, в результате чего возникло «отставание от США» (источник).

У японцев собственная уникальная культура использования сотовых телефонов

Во всём мире массовое распространение смартфонов началось с iPhone, но Япония опережала этот тренд, только в одиночку. К примеру, в этой статье говорится:

В 1999 году японские сотовые телефоны уже имели функцию обмена электронной почтой. В 2000 появились камерофоны. В 2001 были разработаны сети третьего поколения (3G). В 2002 людям стало доступно полноценное скачивание музыки. В 2004 появились электронные платежи, а в 2005 — цифровое ТВ.

В итоге культура смартфонов развивалась в Японии параллельным путём, отдельно от остального мира. Японцы не стремились к использованию в интерфейсе крупных картинок, и текст считался даже более приемлемым, так как люди привыкли к такому дизайну изначально.

А поскольку Япония на этом пути оказалась одинока, сложно привести сравнения. Давайте лучше посмотрим на эволюцию сайтов во времени. Если бы на японцев повлияла глобальная революция в мире смартфонов, то мы бы увидели изменение в их веб-дизайне где-то в 2010 году. Но они остались верны своей традиции, причём почти до смешного.

Япония
2007 vs 2012
2007 vs 2012
2008 vs 2013
2008 vs 2013
2009 vs 2015
2009 vs 2015

Данные взяты с web.archive.org

США
2001 vs 2012
2001 vs 2012
2005 vs 2012
2005 vs 2012
2008 vs 2015
2008 vs 2015

Данные с webdesignmuseum.org

Очевидно, что ни появление iPhone, ни смартфонов в целом не повлияло на Японию так, как повлияло на остальной мир (по меньшей мере на англоговорящую его часть).

Судя по отличиям в дизайне на начало 2000-х годов, Япония довольно долго двигалась по собственной траектории. Но продолжится ли этот тренд?

Вряд ли. Японцы медленно, но верно движутся в сторону принятия захвативших мир технологий, что объясняется двумя главными причинами. Во-первых, это интересно молодёжи (о чём рассказывается в этом видео), а во-вторых, разработчики просто прекращают поддержку старых продуктов, поэтому выбора банально не остаётся.

Методология анализа

Весь мой проект состоял из трёх этапов: сбор данных, их обработка и анализ.

Сбор данных

Этот этап я начала с использования сервиса Open.Trends от SEM Rush, который помог мне найти самые популярные сайты в каждой стране по всем отраслям. Это, конечно, можно было сделать вручную, но я решила автоматизировать процесс с помощью библиотек BeautifulSoup и Selenium для Python (в этом случае также подойдёт Requests, но у меня уже была импортирована Selenium). Вот фрагменты псевдокода, чтобы вы могли понять суть:

# Получает список стран, которые выдал сервис Open.Trends.
countries = getCountries()

# Инициализирует словарь для хранения полученной информации.
d = {
    'country':[],
    'website':[],
    'visits':[]
}

# Перебирает созданный список.
for country in countries:
  # Формирует URL по шаблону сервиса semrush, подставляя код страны с помощью форматированной строки.
  url = f'https://www.semrush.com/trending-websites/{country}/all'

  # Переходит по созданному URL с помощью Selenium WebDriver
  driver.get(url)

  # Передаёт информацию со страницы в BeautifulSoup.
  soup = BeautifulSoup(driver.page_source, 'html.parser')

  # Извлекает табличные данные с помощью BeautifulSoup.
  results = getTableData(soup)

  # Помещает результаты в словарь.
  d['country'] = results['country']
  d['website'] = results['website']
  d['visits'] = results['visits']

# Сохраняет всё в файл.
df = pandas.DataFrame(d)
df.save_csv('popular_websites.csv', index=False)

На заметку. Качество собранных данных зависит от точности методов SEM rush. Я не стала углубляться в этот вопрос, так как полученные списки сайтов были схожи с теми, которые выдавали другие сервисы.

Теперь у меня был словарь с самыми популярными сайтами в каждой стране. Многие из этих сайтов относились к порно, либо мошенническим, а иногда и к тому, и к другому. Так что нужно было отфильтровать их, используя Cyren URL Lookup API. Этот сервис работает «на основе эвристики, машинного обучения и анализа человеком».

Вот псевдокод для этой операции:

# Перебираем все найденные сайты.
for i in range(len(df['website'])):
  # Выбираем сайт.
  url = df.loc[i,'website']
  # Вызываем ��ля этого сайта API.
  category = getCategory(url)
  # Сохраняет результаты.
  df.loc[i,'category'] = category

# Отфильтровываем все ненужные категории. 
undesireable = [...]
df = df.loc[df['category'] in undesireable]

# Сохраняем датафрейм, чтобы не пришлось проделывать всё снова.
df.save_csv('popular_websites_filtered.csv', index=False)

На заметку. Cyren URL Lookup API предоставляет 1 000 бесплатных запросов в месяц на одного пользователя.

Попутный совет. Можно создавать временные адреса e-mail с помощью сервисов вроде temp-mail.

Теперь пора наделать скриншотов сайтов. Если хотите запечатлеть страницу целиком, то вам понадобится Firefox WebDriver из той же Selenium. Если же нет, то сгодится любой WebDriver. Хотя вы вряд ли решите делать полноэкранные скриншоты, так как размер страниц может сильно отличаться, что сделает итоговые результаты менее понятными.

def acceptCookies(...):
  # Эта функция наверняка будет состоять из множества блоков try-except,
  # стараясь найти кнопку с надписью «accept/agree/allow cookies» на каждом языке.
  # Честно говоря, я сдалась где-то на трети пути. 

def notBot(...):
  # Некоторые сайты, прежде чем показывать своё содержимое, потребуют ввести капчу. 
  # Есть разные способы обойти эту капчу.
  # Я даже не пробовала, но вам советую.

# Перебирает все сайты.
for i in range(len(df['website'])):
  url = df.loc[i,'website]
  driver.get(url)

  # Ожидает загрузки страницы.
  # Я использовала статические вызовы sleep, но вам этого, пожалуй, делать не стоит.
  sleep(5)
  notBot(driver)
  sleep(2)
  acceptCoookies(driver)
  sleep(2)

  # Делает скриншоты
  driver.save_screenshot(f'homepage_{country.upper()}_{url}.png')

  # Этот вызов существует только для Firefox WebDriver.
  driver.save_full_page_screenshot(f'homepage_{country.upper()}_{url}.png')

Обработка данных

Я ориентировалась на это руководство с LearnOpenCV, написанное Георгием Серебряковым. В нём для извлечения признаков изображения используется реализация модели ResNet. Вы можете взять код из его статьи, но скриншоты нам нужно загружать иначе. Например, использовать метод, предложенный andrewjong (источник). Для этого потребуется сохранять пути к сохранённым изображениям, которые мы будем использовать в итоговой визуализации.

class ImageFolderWithPaths(datasets.ImageFolder):
    """Кастомный датасет, включающий пути к файлам изображений. Наследует и расширяет
    torchvision.datasets.ImageFolder
    """

    # Переопределяет метод getitem, который вызывает dataloader.
    def getitem(self, index):
        # Обычно ImageFolder возвращает следующий кортеж. 
        original_tuple = super(ImageFolderWithPaths, self).__getitem__(index)
        # Путь к файлу изображения.
        path = self.imgs[index][0]
        # Создаёт новый кортеж, содержащий исходный и путь к файлу. 
        tuple_with_path = (original_tuple + (path,))
        return tuple_with_path

Теперь можно загрузить изображения с помощью этого метода.

# Определяет путь, по которому находятся все изображения.
# Если вы решите разметить скриншоты по странам, то нужно рассортировать их по каталогам.

root_path = '...'

# Преобразует данные, чтобы они имели одинаковую форму.
transform = transforms.Compose([transforms.Resize((255, 255)),
                                 transforms.ToTensor()])
dataset = ImageFolderWithPaths(root, transform=transform)

# Загружает данные.
dataloader = torch.utils.data.DataLoader(dataset, batch_size=32, shuffle=True)

Теперь инициализируем и запускаем модель. Мне пришлось слегка адаптировать код Серебрякова, чтобы использовать другой способ загрузки изображений.

# Инициализирует модель.
model = ResNet101(pretrained=True)
model.eval()
model.to(device)

# Инициализирует переменные для хранения результатов.
features = None
labels = []
image_paths = []

# Запускает модель.
for batch in tqdm(dataloader, desc='Running the model inference'):

  images = batch[0].to('cpu')
  labels += batch[1]
  image_paths += batch[2]

  output = model.forward(images)
  # Преобразует тензор в массив numpy.
  current_features = output.detach().numpy()

  if features is not None:
      features = np.concatenate((features, current_features))
  else:
      features = current_features

# Возвращает метки классов в их строковом виде.
labels = [dataset.classes[e] for e in labels]

# Сохраняет данные.
np.save('images.npy', images)
np.save('features.npy', features)
with open('labels.pkl', 'wb') as f:
  pickle.dump(labels, f)
with open('image_paths.pkl', 'wb') as f:
  pickle.dump(image_paths, f)

Теперь у нас должно получиться 4 набора данных, содержащих пути к скриншотам, метки, сами скриншоты и извлечённые из них признаки.

Анализ данных

Анализ начнём с того, что прогоним данные через реализацию t-SNE из scikit-learn. Так мы редуцируем многомерные массивы признаков до двухмерных координат, которые можно будет отобразить на графике. В эти координаты мы отобразим скриншоты поменьше, чтобы увидеть, как модель распределила сайты.

# S в t-SNE означает стохастический (то есть случайный). 
# Установка начального значения для генератора случайных чисел, чтобы можно было воспроизводить результаты.
seed = 10
random.seed(seed)
torch.manual_seed(seed)
np.random.seed(seed)

# Запуск tsne.
n_components = 2
tsne = TSNE(n_components)
tsne_result = tsne.fit_transform(features)

# Масштабирование и перенос координат, чтобы они вписывались в диапазон [0; 1].
tx = scale_to_01_range(tsne_result[:,0])
ty = scale_to_01_range(tsne_result[:,1)

# Выводит изображения на графике.
for image_path, image, x, y in zip(image_paths, images, tx, ty):
  # Считывание изображения.
  image = cv2.imread(image_path)

  # Изменение размера.
  image = cv2.resize(image, (150,100))

  # Вычисление размеров изображения на основе его координат, полученных через tsne.
  tlx, tly, brx, bry = compute_plot_coordinates(image, x, y)

  # Размещение изображения по его координатам с помощью индексов среза массива numpy.
  tsne_plot[tl_y:br_y, tl_x:br_x, :] = image

cv2.imshow('t-SNE', tsne_plot)
cv2.waitKey()

Теперь во всей этой общей мешанине можно искать любые паттерны. Я свои находки описала в начале статьи.

t-SNE visualization of website screenshotos.
Визуализация скриншотов сайтов с помощью t-SNE

Я также хотела проанализировать данные через призму отличий в письменности, культуре (с географической и экономической стороны) и технологиях, поэтому нашла датасеты, где эта информация содержится: системы письма, список стран с их кодами ISO и регионов, разделение на глобальный Север и глобальный Юг. Потребовалось дополнить их информацией из Google, чтобы в итоговом датасете точно присутствовали метки для всех стран.

Ниже я в общих чертах описала, как использовала эти данные для анализа.

analysis_data = # Импорт данных.

# Инициализация списка для создания параллельного набора меток,
# чтобы вместо названия для разметки стран можно было использовать их системы письма и прочие характеристики.
new_labels = []

# Перебор прежних меток и использование их для создания new_labels.
for label in labels:
  # Выбор new_label на основе старой метки (названия страны).
  new_label = analysis_data['country' == label]
  new_labels.append(new_label)

# Использование new_labels для раскрашивания точек графика рассеяния, построенного по результатам tsne.
tsne_df = pd.DataFrame({'tsne_1': tx, 'tsne_2': ty, 'label': new_labels})
sns.scatterplot(x='tsne_1', y='tsne_2', data=tsne_df, hue='label')

На заметку. Версия о технологической уникальности проверялась количественным методом анализа и здесь не отражена.

Результаты этого анализа показаны в предыдущих разделах.

tsne implementation comparing poopular websites in japan and the usa.
Результат сравнения популярных сайтов в Японии и США с помощью t-SNE