Trong phần này, bạn sẽ học cách kết hợp lấy dữ liệu của Wikipedia thông qua API. Mặc định bạn đã biết cài đặt Python trên máy tính và sử dụng được Python.

Từ phần này về sau, Python sử dụng đều là phiên bản 3.x.x, code Python phiên bản 2.x.x đã không quá phổ biến.

Tất cả đoạn code sau đều lấy dữ liệu trả về có định dạng JSON do tính ưu việt của cấu trúc này, chỉ 1 số ít trường hợp hi hữu thì bạn mới dùng XML.

1. Code chung + ví dụ lấy danh sách các thể loại

import requests
import json

def get_data_by_url(link):
    """
        lấy dữ liệu web bằng link thông qua gói có sẵn "requests"
            link: liên kết web (url) - string
            return: dữ liệu trả về - string
    """
    
    response = requests.get(link, timeout = 30) # timeout là 30 giây
    data = response.text
    return data

def convert_to_json(text):
    """
        chuyển dữ liệu văn bản sang json bằng gói có sẵn "json"
            text: văn bản - string
            return: dữ liệu trả về - dict (cấu trúc từ điển của Python)
    """
    
    root = json.loads(text)
    return root

def get_category_by_page(page_name):
    """
        Lấy danh sách thể loại theo tên cho trước
            page_name: tên trang - string
            return: danh sách thể loại - list
    """

    url = 'https://vi.wikipedia.org/w/api.php?action=query&format=json&formatversion=2&prop=categories&cllimit=max&clshow=!hidden&redirects=true'
    url += '&titles=' + page_name

    data = get_data_by_url(url)
    root = convert_to_json(data)

    # lấy danh sách trang (thường chỉ 1 trang trả về)
    try:
        pages = root['query']['pages']
    except:
        # bắt lỗi nếu có, trả về danh sách rỗng
        return []

    # nếu nhiều hơn 1 trang trả về danh sách rỗng, ý định chỉ lấy thể loại của 1 trang
    if (len(pages) > 1): return []

    cat_list = []
    for p in pages:
        for c in p['categories']:
            # ns: namespace, title: tên tiêu đề thể loại
            cat_list.append(c['title'])

    return cat_list          

# .....................................................................
if __name__ == "__main__":

    cat_list = get_category_by_page('Toán học')
    print(cat_list)

# Kết quả 
# ['Thể loại:Khoa học hình thức', 'Thể loại:Khoa học tự nhiên', 'Thể loại:Phân loại chủ đề chính', 'Thể loại:Thuật ngữ toán học', 'Thể loại:Toán học']

2. Lấy nội dung thô của 1 bài viết

Viết trong hàm main() đoạn này:

if __name__ == "__main__":
     raw_content = get_data_by_url('https://vi.wikipedia.org/wiki/Toán học?action=raw')
     print(raw_content) # in ra màn hình console

# Kết quả là nội dung mã nguồn của trang "Toán học" nằm ở liên kết https://vi.wikipedia.org/wiki/Toán học?action=raw

3. Lấy nội dung mã HTML tối giản của 1 bài viết

Đa số lấy mã tối giản để tách thành câu, từ (parsing) để phân tích/phân loại văn bản hơn là sửa đổi dành cho chạy bot.

Tạo thêm 1 hàm get_content_by_page() như sau:

def get_content_by_page(page_name):
    """
        Lấy nội dung trang có mã HTML tối giản
            page_name: tên trang - string
            return: tên trang + nội dung - dict (kiểu từ điển trong Python)
    """

    url = 'https://vi.wikipedia.org/w/api.php?format=json&formatversion=2&action=query&prop=extracts&redirects=true'
    url += '&titles=' + page_name

    data = get_data_by_url(url)
    root = convert_to_json(data)

    # lấy danh sách trang (thường chỉ 1 trang trả về)
    try:
        pages = root['query']['pages']
    except:
        # bắt lỗi nếu có, trả về danh sách rỗng
        return []

    # nếu nhiều hơn 1 trang trả về từ điển rỗng, ý định chỉ lấy nội dung của 1 trang
    if (len(pages) > 1): return {'title':'', 'content':''}

    # tạo 1 từ điển gồm tên trang và nội dung trang với mã HTML tối giản
    result_dict = {}
    result_dict['title'] = pages[0]['title']
    result_dict['content'] = pages[0]['extract']

    return result_dict

Trong hàm main() thì dùng:

if __name__ == "__main__":

    '''raw_content = get_data_by_url('https://vi.wikipedia.org/wiki/Toán học?action=raw')
    print(raw_content)'''

    result_dict = get_content_by_page('Toán học')
    print(result_dict)

Lưu ý bạn sẽ gặp 1 cảnh báo khi truy cập liên kết [1] như sau:

Mã HTML tối giản có thể bị hư hỏng hay không cân bằng do các biên tập viên gây ra lỗi ở nội dung trang trong quá trình biên tập, khi sử dụng nội dung này thì sẽ có những rủi ro nhất định. Vì Wikipedia là dự án cộng tác, bạn phải chấp nhận điều này. Đây cũng là điểm khó nhất khi chạy bot ở Wikipedia để có thể bao quát tất cả các trường hợp.

4. Hàm lấy dữ liệu chuyên nghiệp hơn?

Tạm thời bạn có thể nâng cấp hàm lấy dữ liệu chuyên nghiệp hơn 1 chút như sau:

def get_data_by_url2(link, params):
    """
        lấy dữ liệu web bằng link thông qua gói có sẵn "requests"
            link: liên kết web (url) - string
            params: từ điển chứa các tham số query - dict
            return: dữ liệu trả về - string
    """

    response = requests.get(link, params, timeout = 30)  # timeout là 30 giây
    data = response.text
    return data

Khi đó, khi chạy hàm main(), bạn phải truyền tham số trước. Ví dụ lấy dữ liệu mã HTML tối giản của trang Toán học, hãy so sánh params với liên kết:

https://vi.wikipedia.org/w/api.php?format=json&formatversion=2&action=query&prop=extracts&redirects=true&titles=Toán học
if __name__ == "__main__":

    '''raw_content = get_data_by_url('https://vi.wikipedia.org/wiki/Toán học?action=raw')
    print(raw_content)'''

    '''result_dict = get_content_by_page('Toán học')
    print(result_dict)'''

    language = 'vi' # en: English, vi: Vietnamese
    link = 'https://' + language + '.wikipedia.org/w/api.php'
    title = 'Toán học'
    params = {
        'action': 'query',
        'format': 'json',
        'formatversion': 2,
        'prop': 'extracts',
        'redirects': 'true',
        'titles': title,
        }
    
    data = get_data_by_url2(link, params)
    print(data)

Có phải params chỉ là các khai báo trực quan, dễ kiểm soát và tùy biến hơn so với liên kết cố định?

5. Hàm lấy dữ liệu chuyên nghiệp hơn nữa?

Một cách tiện hơn là bạn sử dụng các param này trực tiếp trong các hàm lấy dữ liệu theo chức năng yêu cầu để mất công khai báo.

def get_data_by_title(title, language = 'en'):
    """
        get data by title (page, category, template)
            title: string - a Wikipedia's page title
            language: string - a syntax for a language
            return: string - xml data
    """
    
    link = 'https://' + language + '.wikipedia.org/w/api.php'
    params = {
        'action': 'query',
        'format': 'json',
        'formatversion': 2,
        'explaintext': True,
        'prop': 'extracts|revisions|pageprops|templates|categories',
        'rvprop': 'content',
        'rvslots': 'main',
        'clshow': '!hidden',
        'cllimit': 'max',
        'titles': title,
        }

    response = requests.get(link, params) # truy vấn lấy dữ liệu
    root = convert_to_json(response.text) # chuyển dữ liệu sang json

    # lấy danh sách trang (thường chỉ 1 trang trả về)
    pages = []
    try:
        pages = root['query']['pages']
    except:
        # bắt lỗi nếu có, trả về từ điển rỗng
        return {}

    # nếu nhiều hơn 1 trang trả về từ diển rỗng, ý định chỉ lấy nội dung của 1 trang
    if (len(pages) > 1): return {}
    
    # lấy dữ liệu, bắt lỗi từng phần để tránh mất dữ liệu hữu ích nếu có
    result_dict = {}

    try: result_dict['title'] = pages[0]['title']
    except: result_dict['title'] = ''
    
    try: result_dict['content'] = pages[0]['revisions'][0]['slots']['main']['content']
    except: result_dict['content'] = ''

    try: result_dict['extract'] = pages[0]['extract']
    except: result_dict['extract'] = ''
        
    try: result_dict['wikibase_item'] = pages[0]['pageprops']['wikibase_item']
    except: result_dict['wikibase_item'] = ''

    # lấy danh sách bản mẫu
    templates = []
    try:
        for t in pages[0]['templates']:
            if (t['ns'] == 10): templates.append(t['title'])
    except: pass
    result_dict['templates'] = templates

    # lấy danh sách thể loại
    categories = []
    try:
        for t in pages[0]['categories']:
            if (t['ns'] == 14): categories.append(t['title'])
    except: pass
        
    result_dict['categories'] = categories    
    return result_dict

Hàm ở trên dùng để lấy nội dung của bất kỳ 1 trang của Wikipedia theo tên. Trong biến từ điển params cần lưu ý:

  • prop: là kiểu dữ liệu lấy
    • extracts: mã HTML rút gọn
    • revisions: mã HTML, \n thay cho xuống dòng
    • pageprops: lấy thêm thông tin khác như wikibase_item (ID của Wikidata), defaultsort,...
    • templates: danh sách bản mẫu được nhúng vào trang, namespace (ns) là 10
    • categories: danh sách thể loại, namespace (ns) là 14

Lưu ý khái niệm trang bao gồm bài viết, thể loại, bản mẫu, hay bất cứ nội dung gì. Ở hàm main(), bạn chỉ cần viết:

if __name__ == "__main__":
    # lấy nội dung trang tên là 'Word2vec' ở tiếng Việt, thay "en" để chỉ dự án tiếng Anh hay bất cứ tên viết tắt dự án ngôn ngữ nào bạn muốn. Lưu ý có thể không dùng được (tôi chưa thử) với Wikidata hay các dự án chị em khác.
    data_dict = get_data_by_title('Word2vec', 'vi')
    print(data_dict)
    # chuyển dữ liệu này sang json, lấy dữ liệu và phân tích theo yêu cầu
    # ...
    # ...

6. Vẫn chưa thỏa mãn?

That's your business, not mine!

7. Lý do không có code chuyên nghiệp hơn?

Tất nhiên có những đoạn mã chuyên nghiệp hơn, nhưng vì vài lý do nên sẽ không chia sẻ hoặc viết sau:

  • Hơi khó hiểu cho những bạn không hiểu về lập trình, sẽ rất mất thời gian giải thích
  • Code có bản quyền hoặc code riêng theo dự án