Поисковый робот Python | Сбор информации о магазине для веб-сайта обзора

Python

Это 9-й день моего участия в августовском испытании обновлений.Подробности о мероприятии:Испытание августовского обновления

1. Краткое описание

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

в результатах поиска спереворачивать страницыв видеrequest.get()Можно получить данные страницы, а затем выполнить соответствующий анализ данных страницы, чтобы получить необходимую нам информацию о магазине.

Однако в процессе сканирования мы обнаружим, что такая информация, как оценки магазинов, потребление на душу населения и адреса магазинов, отображается на веб-странице как, в получаемых данных аналогично&#xf622, я не знаю, что это такое. Это на самом деле своего рода антилазинг шрифтов, а дальше мы их разберем по порядку.

Вот поля данных, которые нам нужно собрать:

поле инструкция способ получения шрифт
shop_id Идентификатор магазина Получите это напрямую
shop_name название магазина Получите это напрямую
shop_star магазин звезда Получите это напрямую
shop_address Адрес магазина Получите это напрямую
shop_review Отзывы о магазине Антисканирование шрифта shopNum
shop_price Потребление магазинов на душу населения Антисканирование шрифта shopNum
shop_tag_site Район, где находится магазин Антисканирование шрифта tagName
shop_tag_type Классификация магазинов Антисканирование шрифта tagName

2. Обработка шрифтов против сканирования

Открываем Dianping, ищем лыжи, нажимаем F12 на странице результатов поиска, чтобы войти в режим разработчика, выбираем количество оценок, чтобы увидеть ихкласс — shopNum, а содержимое — □,существуетВ стилях справа видно, что это семейство шрифтов PingFangSC-Regular-shopNum.. Фактически, щелкните ссылку .css справа, чтобы найти ссылку на файл шрифта. Учитывая, что ссылки на файл шрифта, соответствующие другой информации поля, связанной с антисканированием шрифта, могут отличаться, мы собираем еще один метод для одноразового получения (подробности см. В следующем абзаце).

字体反爬(评价数)

2.1. Получите ссылку на файл шрифта

мы вглава страницыраздел, можно найтиГрафика и текст css, а соответствующий адрес css содержит те, которые будут использоваться позжеСсылки на все файлы шрифтов, напрямую используйте request.get(), чтобы запросить изменение адреса, чтобы вернуть все имена шрифтов и ссылки для загрузки их файлов шрифтов.

字体反爬(字体链接)

- Определите метод функции get_html() для получения данных веб-страницы.

# 获取网页数据
def get_html(url, headers):   
    try:
        rep = requests.get(url ,headers=headers)
    except Exception as e :
        print(e)
    text = rep.text
    html = re.sub('\s', '', text) #去掉非字符数据
    
    return html

- Получить данные веб-страницы

import re
import requests
# Cookie部分,直接复制浏览器里的即可
headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36",
        "Cookie":"你的浏览器Cookie",
        }
# 搜索关键字
key = '滑雪'
# 基础url
url = f'https://www.dianping.com/search/keyword/2/0_{key}'
# 获取网页数据
html = get_html(url, headers)

- Получить ссылку на файл шрифта

# 正则表达式获取head里图文混排css中的字体文件链接
text_css = re.findall('<!--图文混排css--><linkrel="stylesheet"type="text\/css"href="(.*?)">', html)[0]  
# 'http://s3plus.meituan.net/v1/mss_0a06a471f9514fc79c981b5466f56b91/svgtextcss/29de4c2bc5d95d1e147c3c25a5f4aad8.css'
# 组合成css链接
css_url = 'http:' + text_css
# 获取字体文件链接的网页数据
font_html = get_html(css_url, headers)
# 正则表达式获取 字体信息列表
font_list = re.findall(r'@font-face{(.*?)}', font_html)

# 获取使用到的字体及其链接
font_dics = {}
for font in font_list:
    # 正则表达式获取字体文件名称
    font_name = re.findall(r'font-family:"PingFangSC-Regular-(.*?)"', font)[0]
    # 正则表达式获取字体文件对应链接
    font_dics[font_name] = 'http:' + re.findall(r',url\("(.*?)"\);', font)[0]

- Загрузите файлы шрифтов на локальный

# 由于我们用到的只有shopNum、tagName和address,这里只下载这三类字体
font_use_list = ['shopNum','tagName','address']
for key in font_use_list:
    woff = requests.get(font_dics[key], headers=headers).content
    with open(f'{key}.woff', 'wb')as f:
        f.write(woff)
  • Файл шрифта (хранится локально, устанавливаетсяFontCreatorВы можете открывать файлы шрифтов, просматривать содержимое шрифтов и отвечатьFontCreatorВы можете получить адрес загрузки установочного пакета)

字体文件

2.2 Создайте отношение сопоставления между тремя типами шрифтов и реальными символами.

Давайте сначала посмотрим на html-содержимое оценочного номера в запрошенных данных веб-страницы следующим образом:

<b>
    <svgmtsi class="shopNum">&#xf8a1;</svgmtsi>
    <svgmtsi class="shopNum">&#xee4c;</svgmtsi>
    <svgmtsi class="shopNum">&#xe103;</svgmtsi>
    <svgmtsi class="shopNum">&#xe62a;</svgmtsi>
</b>条评价

Соответствующая веб-страница показывает количество оценок как4576оценки, мы знаем, что соответствующее отношение4=&#xf8a1,5=&#xee4c,7=&#xe103,6=&#xe62a.

мы используемFontCreatorОткройте файл шрифта shopNum следующим образом:

shopNum

Сравнивая, мы можем найти, что4 соответствует SHOPNUM, UNIF8A1, 5 соответствует UNIEE4C...и т.д. Итак, чтобы найти правило, мы знаем, что соответствующая информация о данных в запрошенных данных такая, как&#xf8a1ФактическиuniF8A1Реальный соответствующий номер или текст должен соответствовать определенному символу (4) в файле шрифта.

Здесь нам нужно представить стороннюю библиотеку обработки шрифтов Python.fontTools, измените отношение сопоставления трех типов шрифтов:

from fontTools.ttLib import TTFont

# 修改三类字体映射关系
real_list = {}
for key in font_use_list:
    # 打开本地字体文件
    font_data = TTFont(f'{key}.woff')
    # font_data.saveXML('shopNum.xml')
    # 获取全部编码,前2个非有用字符去掉 
    uni_list = font_data.getGlyphOrder()[2:]
    # 请求数据中是 "&#xf8a1" 对应 编码中为"uniF8A1",我们进行替换,以请求数据为准
    real_list[key] = ['&#x' + uni[3:] for uni in uni_list]

real_list

Открыв эти три типа файлов шрифтов, мы обнаружили, что соответствующий порядок символов одинаков (порядок и содержимое символов), а копия выглядит следующим образом:

# 字符串
words = '1234567890店中美家馆小车大市公酒行国品发电金心业商司超生装园场食有新限天面工服海华水房饰城乐汽香部利子老艺花专东肉菜学福饭人百餐茶务通味所山区门药银农龙停尚安广鑫一容动南具源兴鲜记时机烤文康信果阳理锅宝达地儿衣特产西批坊州牛佳化五米修爱北养卖建材三会鸡室红站德王光名丽油院堂烧江社合星货型村自科快便日民营和活童明器烟育宾精屋经居庄石顺林尔县手厅销用好客火雅盛体旅之鞋辣作粉包楼校鱼平彩上吧保永万物教吃设医正造丰健点汤网庆技斯洗料配汇木缘加麻联卫川泰色世方寓风幼羊烫来高厂兰阿贝皮全女拉成云维贸道术运都口博河瑞宏京际路祥青镇厨培力惠连马鸿钢训影甲助窗布富牌头四多妆吉苑沙恒隆春干饼氏里二管诚制售嘉长轩杂副清计黄讯太鸭号街交与叉附近层旁对巷栋环省桥湖段乡厦府铺内侧元购前幢滨处向座下澩凤港开关景泉塘放昌线湾政步宁解白田町溪十八古双胜本单同九迎第台玉锦底后七斜期武岭松角纪朝峰六振珠局岗洲横边济井办汉代临弄团外塔杨铁浦字年岛陵原梅进荣友虹央桂沿事津凯莲丁秀柳集紫旗张谷的是不了很还个也这我就在以可到错没去过感次要比觉看得说常真们但最喜哈么别位能较境非为欢然他挺着价那意种想出员两推做排实分间甜度起满给热完格荐喝等其再几只现朋候样直而买于般豆量选奶打每评少算又因情找些份置适什蛋师气你姐棒试总定啊足级整带虾如态且尝主话强当更板知己无酸让入啦式笑赞片酱差像提队走嫩才刚午接重串回晚微周值费性桌拍跟块调糕'

Для числового класса (на самом деле их всего 10, которые расположены в карте шрифта и в верхней 10 части строки слов), когда мы получаем антисканирующие символы как&#xf8a1ФактическиuniF8A1час,Сначала мы находим его соответствующую позицию в shopNum, а затем заменяем символы в строке слов на ту же позицию..

for i in range(10):
	s.replace(real_list['shopNum'][i], words[i])

Для класса китайских символов (не более len(real_list['tagName'])) логика замены аналогична логике цифрового класса, и можно заменить ту же позицию.

for i in range(len(real_list['tagName'])):
    s.replace(real_list['tagName'][i], words[i])

3. Анализ информации одностраничного магазина

Благодаря обработке шрифта, предотвращающего лазание во второй части, в сочетании с полями информации о магазине, которые можно получить напрямую, мы можем завершить анализ и сбор всей информации о магазине. Здесь мы используемрегулярное выражение для разбораХа, заинтересованные студенты также могут использовать xpath, bs4 и другие библиотеки инструментов для обработки.

Мы создаем функцию для получения всех данных о магазине на одной странице.get_items(html, real_list, words):

# 获取单页全部信息
def get_items(html, real_list, words):    
    # 获取单页全部商铺html整体信息
    shop_list = re.findall(r'<divclass="shop-listJ_shop-listshop-all-list"id="shop-all-list">(.*)<\/div>',html)[0]
    # 获取单页全部商铺html信息组成的列表
    shops = re.findall(r'<liclass="">(.*?)<\/li>', shop_list)
    
    items = []
    for shop in shops:
        # 解析单个商铺信息
        # shop = shops[0]
        item = {}
        # 商铺id(唯一性,用于数据清洗阶段去重)
        item['shop_id'] = re.findall(r'<divclass="txt"><divclass="tit">.*data-shopid="(.*?)"', shop)[0]
        # 商铺名称
        item['shop_name'] = re.findall(r'<divclass="txt"><divclass="tit">.*<h4>(.*)<\/h4>', shop)[0]
        # 商铺星级,由于是二位数,需要除以10.0转化为浮点数
        item['shop_star'] = re.findall(r'<divclass="nebula_star"><divclass="star_icon"><spanclass="starstar_(\d+)star_sml"><\/span>', shop)[0]
        item['shop_star'] = int(item['shop_star'])/10.0
        
        # 其实关于商铺地址信息,在class="operate J_operate Hide"中的data-address是有的
        # 因此,我们不需要用到 字体反爬,直接正则获取吧
        # 商铺地址
        item['shop_address'] = re.findall('<divclass="operateJ_operateHide">.*?data-address="(.*?)"', shop)[0]
        
        shop_name = item['shop_name']
        # 评价数和人均价格,用的是shopNum
        try:
            shop_review = re.findall(r'<b>(.*?)<\/b>条评价', shop)[0]
        except:
            print(f'{shop_name} 无评价数据')
            shop_review = ''
            
        try:
            shop_price = re.findall(r'人均<b>¥(.*?)<\/b>', shop)[0]
        except:
            print(f'{shop_name} 无人均消费数据')
            shop_price = ''
            
        for i in range(10):
            shop_review = shop_review.replace(real_list['shopNum'][i], words[i])
            shop_price = shop_price.replace(real_list['shopNum'][i], words[i])
        # 评价数和人均价格,只取数字,然后组合起来
        item['shop_review'] = ''.join(re.findall(r'\d',shop_review))
        item['shop_price'] = ''.join(re.findall(r'\d',shop_price))
        
        # 商铺所在区域和商铺分类用的是tagName
        shop_tag_site = re.findall(r'<spanclass="tag">.*data-click-name="shop_tag_region_click"(.*?)<\/span>', shop)[0]
        # 商铺分类
        shop_tag_type = re.findall('<divclass="tag-addr">.*?<spanclass="tag">(.*?)</span></a>', shop)[0]
        for i in range(len(real_list['tagName'])):
            shop_tag_site = shop_tag_site.replace(real_list['tagName'][i], words[i])
            shop_tag_type = shop_tag_type.replace(real_list['tagName'][i], words[i])
        # 匹配中文字符的正则表达式: [\u4e00-\u9fa5]
        item['shop_tag_site'] = ''.join(re.findall(r'[\u4e00-\u9fa5]',shop_tag_site))
        item['shop_tag_type'] = ''.join(re.findall(r'[\u4e00-\u9fa5]',shop_tag_type))
        items.append(item)
    
    return items

Ниже приведены все информационные данные магазина на главной странице, полученные на примере катания на лыжах:

某页全部店铺信息

4. Получить все данные страницы

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

4.1 Получите количество страниц данных

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

页数

# 获取页数
def get_pages(html):
    try:
        page_html = re.findall(r'<divclass="page">(.*?)</div>', html)[0]
        pages = re.findall(r'<ahref=.*>(\d+)<\/a>', page_html)[0]
    except :
        pages = 1
    
    return pages

4.2. Соберите все данные

Когда мы анализируем данные веб-страницы на первой странице, мы можем получить количество страниц данных, загрузить антисканирующий шрифт, получить фактическое отношение сопоставления шрифта real_list и строковые слова, состоящие из реальных символов, и в то же время, мы также получаем весь состав данных магазина первой страницы списка. Затем мы можем перейти от второй страницы к последней странице и добавить полученные одностраничные данные в первый список.

# 第一页商铺数据构成的列表
shop_data = get_items(html, real_list, words)
# 从第二页开始遍历到最后一页
for page in range(2,pages+1):
    aim_url = f'{url}/p{page}'
    html = get_html(aim_url, headers)
    items = get_items(html, real_list, words)
    shop_data.extend(items)
    print(f'已爬取{page}页数据')
# 转化为dataframe类型
df = pd.DataFrame(shop_data)

Тип кадра данных, составленный из всех полученных данных, выглядит следующим образом:

全部结果

5. Резюме

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

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

Полный код будет отправлен в фоновом режиме официального аккаунта.0104Вы можете получить это~

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