Используйте сканеры Python для фильтрации результатов поиска по ключевым словам «Самородки».

Python рептилия

Я только начал изучать Python в эти несколько дней. Это похоже на написание небольшого проекта краулера, чтобы потренировать свои руки. Поскольку я перешел в Nuggets после инцидента с «XX Dolphin» в Цзяньшу, я чувствую, что Nuggets проделали хорошую работу во всех аспектах. , особенно статья.Качество и редактор, написавший статью, очень комфортно поработали.

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

关键字为'Python',点赞数设为10

Собственно работа начинается:

1. Состав проекта

项目构成
Программа в основном делится на: controller (главный контроллер), downloader (загрузчик), parser (парсер), url_manager (менеджер URL), outputer (устройство вывода).

2. Менеджер URL-адресов (url_manager)

Менеджер URL-адресов в основном отвечает за создание и поддержку ссылок веб-сайтов, которые необходимо сканировать. Что касается «самородков» веб-сайтов, которые мы хотим сканировать, они в основном делятся на две категории: URL-адреса статических страниц и страницы, динамически создаваемые AJAX.

Состав URL-адресов двух запросов сильно различается, и возвращаемый контент также отличается: URL-адрес статической страницы возвращает HTML-страницу, а запрос AJAX возвращает строку JSON. Для этих двух методов доступа мы можем написать менеджер URL следующим образом:

class UrlManager(object):
    def __init__(self):
        self.new_urls = set()   # 新的url的集合

    # 构建访问静态页面的url
    def build_static_url(self, base_url, keyword):
        static_url = base_url + '?query=' + keyword
        return static_url

    # 根据输入的base_url(基础地址)和params(参数字典)来构造一个新的url
    # eg:https://search-merger-ms.juejin.im/v1/search?query=python&page=1&raw_result=false&src=web
    # 参数中的start_page是访问的起始页数字,gap是访问的页数间隔
    def build_ajax_url(self, base_url, params, start_page=0, end_page=4, gap=1):
        if base_url is None:
            print('Invalid param base_url!')
            return None
        if params is None or len(params)==0:
            print('Invalid param request_params!')
            return None
        if end_page < start_page:
            raise Exception('start_page is bigger than end_page!')
        equal_sign = '='    #键值对内部连接符
        and_sign = '&'  #键值对之间连接符
        # 将base_url和参数拼接成url放入集合中
        for page_num in range(start_page, end_page, gap):
            param_list = []
            params['page'] = str(page_num)
            for item in params.items():
                param_list.append(equal_sign.join(item))    # 字典中每个键值对中间用'='连接
            param_str = and_sign.join(param_list)       # 不同键值对之间用'&'连接
            new_url = base_url + '?' + param_str
            self.new_urls.add(new_url)
        return None

    # 从url集合中获取一个新的url
    def get_new_url(self):
        if self.new_urls is None or len(self.new_urls) == 0:
            print('there are no new_url!')
            return None
        return self.new_urls.pop()

    # 判断集合中是否还有url
    def has_more_url(self):
        if self.new_urls is None or len(self.new_urls) == 0:
            return False
        else:
            return True

Как и в приведенном выше коде, коллекция поддерживается в функции инициализации __init__, и созданный URL-адрес будет помещен в эту коллекцию. Затем, в соответствии со структурой URL-адреса, он делится на базовый URL-адрес + параметры доступа, которые связаны знаком «?», а параметры связаны знаком «&». Соедините их с помощью функции build_ajax_url, чтобы сформировать полный URL-адрес и поместить его в коллекцию.

2. Загрузчик (html_downloader)

import urllib.request

class HtmlDownloader(object):
    def download(self, url):
        if url is None:
            print('one invalid url is found!')
            return None
        response = urllib.request.urlopen(url)
        if response.getcode() != 200:
            print('response from %s is invalid!' % url)
            return None
        return response.read().decode('utf-8')

Этот код относительно прост и использует библиотеку urllib для доступа к URL-адресу и возврата результирующих возвращаемых данных.

3. Парсер JSON (json_parser)

import json
from crawler.beans import result_bean


class JsonParser(object):
    # 将json字符创解析为一个对象
    def json_to_object(self, json_content):
        if json_content is None:
            print('parse error!json is None!')
            return None
        print('json', str(json_content))
        return json.loads(str(json_content))

    # 从JSON构成的对象中提取出文章的title、link、collectionCount等数据,并将其封装成一个Bean对象,最后将这些对象添加到结果列表中
    def build_bean_from_json(self, json_collection, baseline):
        if json_collection is None:
            print('build bean from json error! json_collection is None!')
        list = json_collection['d'] # 文章的列表
        result_list = []    # 结果的列表
        for element in list:
            starCount = element['collectionCount']  # 获得的收藏数,即获得的赞数
            if int(starCount) > baseline:   # 如果收藏数超过baseline,则勾结结果对象并添加到结果列表中
                title = element['title']
                link = element['originalUrl']
                result = result_bean.ResultBean(title, link, starCount)
                result_list.append(result)      # 添加到结果列表中
                print(title, link, starCount)
        return result_list

Анализ JSON в основном делится на две части: 1. Преобразовать строку JSON в объект словаря 2. Извлечь заголовок статьи, ссылку, лайки и другую информацию из объекта словаря и решить, следует ли инкапсулировать эти данные в объект результата. и добавить его в список результатов.

3. Парсер HTML (html_parser)

Мы можем получить HTML-страницу, посетив: «https://juejin.im/search?query=python», но есть только одна страница данных, что эквивалентно посещению «https://search-merger-ms. juejin.im/v1' Количество данных, полученных /search?query=python&page=0&raw_result=false&src=web', но разница в том, что формат одного возвращаемого контента - HTML, а второго - JSON. Сюда же ставим парсер HTML, который в проекте не нужен:

from bs4 import BeautifulSoup
from crawler.beans import result_bean


class HtmlParser(object):

    # 创建BeautifulSoup对象,将html结构化
    def build_soup(self, html_content):
        self.soup = BeautifulSoup(html_content, 'html.parser')
        return self.soup

    # 根据获得的赞数过滤得到符合条件的tag
    def get_dom_by_star(self, baseline):
        doms = self.soup.find_all('span', class_='count')
        # 根据最少赞数过滤结果,只保留不小于baseline的节点
        for dom in doms:
            if int(dom.get_text()) < baseline:
                doms.remove(dom)
        return doms

    # 根据节点构建结果对象并添加到列表中
    def build_bean_from_html(self, baseline):
        doms = self.get_dom_by_star(baseline)
        if doms is None or len(doms)==0:
            print('doms is empty!')
            return None
        results = []
        for dom in doms:
            starCount = dom.get_text()      # 获得的赞数
            root = dom.find_parent('div', class_='info-box')    #这篇文章的节点
            a = root.find('a', class_='title', target='_blank') #包含了文章题目和链接的tag
            link = 'https://juejin.cn' + a['href'] + '/detail'  #构造link
            title = a.get_text()
            results.append(result_bean.ResultBean(title, link, starCount))
            print(link, title, starCount)
        return results

Для более эффективного парсинга HTML-файлов здесь используется модуль bs4.

4. Объект результата (result_bean)

Объект результата — это инкапсуляция результата сканирования, который инкапсулирует название статьи, соответствующую ссылку и количество полученных лайков в объект:

# 将每条文章保存为一个bean,其中包含:题目、链接、获得的赞数 属性
class ResultBean(object):
    def __init__(self, title, link, starCount=10):
        self.title = title
        self.link = link
        self.starCount = starCount

5. Устройство вывода HTML (html_outputer)

class HtmlOutputer(object):

    def __init__(self):
        self.datas = []     # 輸入結果列表

    # 構建輸入數據(結果列表)
    def build_data(self, datas):
        if datas is None:
            print('Invalid data for output!')
            return None
        # 判断是应该追加还是覆盖
        if self.datas is None or len(self.datas)==0:
            self.datas = datas
        else:
            self.datas.extend(datas)

    # 输出html文件
    def output(self):
        fout = open('output.html', 'w', encoding='utf-8')
        fout.write('<html>')
        fout.write("<head><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">")
        fout.write("<link rel=\"stylesheet\" href=\"http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css\"> ")
        fout.write("<script src=\"http://cdn.static.runoob.com/libs/bootstrap/3.3.7/js/bootstrap.min.js\"></script>")
        fout.write("</head>")
        fout.write("<body>")
        fout.write("<table class=\"table table-striped\" width=\"200\">")

        fout.write("<thead><tr><td><strong>文章</strong></td><td><strong>星数</strong></td></tr></thead>")
        for data in self.datas:
            fout.write("<tr>")
            fout.write("<td width=\"100\"><a href=\"%s\" target=\"_blank\">%s</a></td>" % (data.link, data.title))
            fout.write("<td width=\"100\">    %s</td>" % data.starCount)
            fout.write("</tr>")

        fout.write("</table>")
        fout.write("</body>")
        fout.write("</html>")
        fout.close()

Сохраните данные в списке объектов результата, полученном после парсинга в HTML-таблице.

6. Контроллер (main_controller)

from crawler.url import url_manager
from crawler.downloader import html_downloader
from crawler.parser import html_parser, json_parser
from crawler.outputer import html_outputer


class MainController(object):
    def __init__(self):
        self.url_manager = url_manager.UrlManager()
        self.downloader = html_downloader.HtmlDownloader()
        self.html_parser = html_parser.HtmlParser()
        self.html_outputer = html_outputer.HtmlOutputer()
        self.json_paser = json_parser.JsonParser()

    def craw(self, func):
        def in_craw(baseline):
            print('begin to crawler..')
            results = []
            while self.url_manager.has_more_url():
                content = self.downloader.download(self.url_manager.get_new_url())  # 根据URL获取静态网页
                results.extend(func(content, baseline))
            self.html_outputer.build_data(results)
            self.html_outputer.output()
            print('crawler end..')
        print('call craw..')
        return in_craw

    def parse_from_json(self, content, baseline):
        json_collection = self.json_paser.json_to_object(content)
        results = self.json_paser.build_bean_from_json(json_collection, baseline)
        return results

    def parse_from_html(self, content, baseline):
        self.html_parser.build_soup(content)  # 使用BeautifulSoup将html网页构建成soup树
        results = self.html_parser.build_bean_from_html(baseline)
        return results

В контроллере экземпляры предыдущих модулей создаются через функцию __init__. Функции parse_from_json и parse_from_html отвечают за синтаксический анализ результатов из JSON и HTML соответственно; функция craw использует замыкание для абстрагирования функции синтаксического анализа, чтобы мы могли легко выбрать нужный синтаксический анализатор и передать синтаксический анализатор в качестве параметра func в функция craw, которая похожа на использование интерфейсов в Java, но более гибкая. Конкретное использование в основной функции может быть:

if __name__ == '__main__':
    base_url = 'https://juejin.im/search'    # 要爬取的HTML网站网址(不含参数)
    ajax_base_url = 'https://search-merger-ms.juejin.im/v1/search'      #要通过ajax访问的网址(不含参数,返回JSON)
    keyword = 'python'  # 搜索的关键字
    baseline = 10   # 获得的最少赞数量
    # 创建控制器对象
    crawler_controller = MainController()
    static_url = crawler_controller.url_manager.build_static_url(base_url, keyword)     # 构建静态URL

    # craw_html = crawler_controller.craw(crawler_controller.parse_from_html)     # 选择HTML解析器
    # craw_html(static_url, baseline)   #开始抓取

    # ajax请求的网址例子:'https://search-merger-ms.juejin.im/v1/search?query=python&page=0&raw_result=false&src=web'
    params = {}     # 对应的请求参数
    # 初始化请求参数
    params['query'] = keyword
    params['page'] = '1'
    params['raw_result'] = 'false'
    params['src'] = 'web'
    crawler_controller.url_manager.build_ajax_url(ajax_base_url, params)    # 构建ajax访问的网址
    craw_json = crawler_controller.craw(crawler_controller.parse_from_json) # 选择JSON解析器
    craw_json(baseline)     #开始抓取

Заканчивать