[PyACC 9] Python - Kiến thức nâng cao - P1.1
Mô hình client-server
Định nghĩa
Mô hình Client Server là mô hình mạng máy tính trong đó các máy tính con được đóng vai trò như một máy khách, chúng làm nhiệm vụ gửi yêu cầu đến các máy chủ. Để máy chủ xử lý yêu cầu và trả kết quả về cho máy khách đó.
Trong đó:
Máy tính đóng vai trò là máy khách – Client: Với vai trò là máy khách, chúng sẽ không cung cấp tài nguyên đến các máy tính khác mà chỉ sử dụng tài nguyên được cung cấp từ máy chủ. Một client trong mô hình này có thể là một server cho mô hình khác, tùy thuộc vào nhu cầu sử dụng của người dùng.
Máy tính đóng vai trò là máy chủ – Server: Là máy tính có khả năng cung cấp tài nguyên và các dịch vụ đến các máy khách khác trong hệ thống mạng. Server đóng vai trò hỗ trợ cho các hoạt động trên máy khách client diễn ra hiệu quả hơn.
Nguyên tắc hoạt động
Trong mô hình Client Server, server chấp nhận tất cả các yêu cầu hợp lệ từ mọi nơi khác nhau trên Internet, sau đó trả kết quả về máy tính đã gửi yêu cầu đó
Máy tính được coi là máy khách khi chúng làm nhiệm vụ gửi yêu cầu đến các máy chủ và đợi câu trả lời được gửi về.
Để máy khách và máy chủ có thể giao tiếp được với nhau thì giữa chúng phải có một chuẩn nhất định, và chuẩn đó được gọi là giao thức. (Giao thức là gì thì các bạn có thể tham khảo tại đây) Một số giao thức được sử dụng phổ biến hiện nay như: HTTPS, TCP/IP, FTP,...
Nếu máy khách muốn lấy được thông tin từ máy chủ, chúng phải tuân theo một giao thức mà máy chủ đó đưa ra. Nếu yêu cầu đó được chấp nhận thì máy chủ sẽ thu thập thông tin và trả về kết quả cho máy khách yêu cầu. Bởi vì Server - máy chủ luôn luôn trong trạng thái sẵn sàng để nhận request từ client nên chỉ cần client gửi yêu cầu thì server sẽ trả kết quả về phía client trong thời gian ngắn nhất.
Ví dụ thực tế
Các bạn có thể đã quen với việc đọc báo trên VNExpress, Dantri; xem bóng đá trên 24h.com.vn; xem video trên Youtube, lướt Face, xem Tiktok...
Tất cả website trên đều có máy chủ - server đang được đặt ở một nơi nào đó (có thể rất xa nơi chúng ta đang ngồi). Những máy chủ này được dùng để lưu trữ dữ liệu, tính toán xử lý những yêu cầu được gửi từ những thiết bị điện thoại hay trên máy tính của chúng ta.
Với số lượng người dùng lớn, máy chủ không chỉ giới hạn ở 1 chiếc máy tính có cấu hình cao nữa, nó có thể là một hệ thống máy tính có cấu hình cực khủng; thay vì nằm ở một địa điểm, chúng có thể nằm ở rất nhiều trung tâm dữ liệu khác nhau với mục đích là ở càng gần máy khách càng tốt nhằm đảm bảo trải nghiệm xuyên suốt của người dùng.
Khoảng cách địa lý giữa máy chủ và máy khách càng cách xa nhau thì càng mất nhiều thời gian truyền dẫn trong đường truyền Internet. Hãy tưởng tượng bạn đang ở HCM, bạn có một gói đồ muốn chuyển đến HN, nhưng bưu cục không phải ở đầu ngõ mà ở cách nhà bạn 10 km, rõ ràng bạn sẽ mất thời gian và công sức để di chuyển đến đó.
Dữ liệu sẽ được luân chuyển thông qua các yêu cầu truy cập được gửi đi từ máy khách - máy tính, điện thoại của chúng ta. Lúc này những máy tính hay điện thoại của chúng ta sẽ được coi là các máy khách - client. Chúng không phục vụ bất kì máy tính nào khác trong mô hình này.
Nếu chúng ta muốn xem danh sách tin tức nóng hổi của ngày hôm nay, nó sẽ yêu cầu đến máy chủ VNExpress. Sau khi tính toán xử lý, máy chủ VNExpress sẽ trả về kết quả cho điện thoại của bạn, kết quả này được hiển thị trên ứng dụng VNExpress hoặc trên website VNExpress nếu bạn đang đọc trên trình duyệt.
Điều này cũng tương tự với tất cả yêu cầu khác.
Bài tập Thu thập dữ liệu
Giả sử bạn muốn tự xây dựng một ứng dụng đọc tin Bitcoin hằng ngày. Đầu tiên bạn cần có nguồn thông tin cung cấp tin tức cho bạn. Nguồn tin này có thể đến từ nhiều trang web khác nhau, ví dụ:
Điều này có nghĩa là bạn sẽ dùng Python viết một cái tool nào đó để biến máy tính của bạn trở thành một máy khách, gửi yêu cầu đến máy chủ của những website trên và lưu trữ kết quả trả về vào một kho dữ liệu nào đó của bạn (CSDL hoặc là file). Cuối cùng lấy dữ liệu đó ra và hiển thị trên app của các bạn.
Hãy bắt đầu với việc đơn giản nhất thu thập danh sách tin tức Bitcoin từ trang https://cointelegraph.com/tags/bitcoin
Bài tập này sẽ giúp bạn làm quen với việc thu thập dữ liệu trên Internet. Và bước đầu tiên bạn cần làm là "Gửi yêu cầu với requests".
Gửi yêu cầu với requests
requests là một thư viện giúp cho bạn có thể gửi các yêu cầu đến các máy chủ và nhận về kết quả tính toán xử lý từ những máy chủ trên.
import requests
url = "https://cointelegraph.com/tags/bitcoin"
headers = {
"authority": "cointelegraph.com",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-language": "en-US,en;q=0.9,vi;q=0.8",
"cache-control": "no-cache",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
response = requests.request("GET", url, headers=headers)
print(response.text)
Bóc tách dữ liệu với lxml, regex
Bạn thấy đấy dữ liệu trả về trong hình trên rất xấu xí, làm thế nào để chúng ta có thể thu gọn chúng thành dạng đẹp đẽ như thế này?
[
{
"title": "Historically accurate Bitcoin metric exits buy zone in 'unprecedented' 2022 bear market",
"url": "https://cointelegraph.com/news/historically-accurate-bitcoin-metric-exits-buy-zone-in-unprecedented-2022-bear-market",
"description": "Bitcoin has historically profited from Puell Multiple lift-offs, but unique macro conditions mean what happens next is uncertain.",
"time": "2022-07-30 17h ago",
"author": "William Suberg",
"view_count": 16827
}
...
...
]
Lúc này lxml phát huy tác dụng. lxml là một thư viện để xử lý XML và HTML. Nhưng để có thể dùng được lxml cho mục đích bóc tách nội dung bạn cần phải biết về một khái niệm tên là XPath.
XPath là gì?
XPath là một ngôn ngữ được thiết kế ra với mục đích giúp cho ứng dụng có thể “di chuyển” bên trong các tệp XML để truy xuất các giá trị, thuộc tính của các element.
Công dụng điển hình bạn có thể dàng nhận ra nhất đó chính là XPath sử dụng các biểu thức đường dẫn để chọn các nút hoặc tập hợp nút trong tài liệu XML. Mà HTML là một biến thể của XML, do đó bạn hoàn toàn có thể sử dụng XPath với HTML.
Cú pháp của XPath ra sao?
XPath chứa đường dẫn của element nằm trên trang web của bạn, sau đây là cấu trúc tiêu chuẩn để tạo XPath:
Ví dụ
Khi muốn lấy nội dung của một thẻ nào đó, ta sẽ kết thúc với //text()
- Lấy nội dung của các thẻ <title>:
//title//text()
- Lấy nội dung của các thẻ <h3>:
//h3//text()
- Lấy nội dung của các thẻ <p>:
//p//text()
- Lấy nội dung của các thẻ <span> nằm trong các thẻ div có class="example":
//div[@class="example"]//span//text()
Lấy nội dung của các thẻ <span> nằm trong các thẻ div có id="example"://div[@id="example"]//span//text()
Khi lấy giá trị của một thuộc tính của một thẻ nào đó, ta sẽ kết thúc biểu thức bằng cách thêm @ ở trước tên thuộc tính, ví dụ:
Bạn muốn lấy giá trị của thuộc tính datetime trong thẻ
<div class="post-card-inline__meta">
<time datetime="2022-07-30" class="post-card-inline__date"> 17 hours ago </time>
</div>
thì bạn có thể biểu thức Xpath thế này:
//div//time[@class="post-card-inline__date"]//@datetime
Các bạn có thể luyện tập thêm với extension chạy trên chrome tên là XPather:
https://chrome.google.com/webstore/detail/xpather/gabekepgockchhemajjahpchlnkadiac?hl=en
Sau khi cài đặt lên trình duyệt, bạn hãy làm theo hướng dẫn như hình bên dưới.
OK. Bây giờ sau khi đã hiểu Xpath là gì, chúng ta hãy dùng lxml để bóc tách dữ liệu từ kết quả trả về.
...
response = requests.request("GET", url, headers=headers)
tree = html.fromstring(response.content.decode(response.encoding))
article_elements = tree.xpath('//li[@class="posts-listing__item"]//article[@class="post-card-inline"]')
articles = []
for element in article_elements:
title = None
url = None
description = None
time = None
author = None
view_count = None
titles = element.xpath('.//a[@class="post-card-inline__title-link"]//span//text()')
if titles:
title = ''.join(titles)
hrefs = element.xpath('.//a[@class="post-card-inline__title-link"]//@href')
if hrefs:
href = ''.join(hrefs)
url = f'https://cointelegraph.com/{href}'
descriptions = element.xpath('.//p[@class="post-card-inline__text"]//text()')
if descriptions:
description = ''.join(descriptions)
time_agos = element.xpath('.//div[@class="post-card-inline__meta"]//time//text()')
time_ago = ''.join(time_agos)
times = element.xpath('.//div[@class="post-card-inline__meta"]//time//@datetime')
time = ''.join(times)
time = time if 'ago' not in time_ago else f'{time} {time_ago}'
authors = element.xpath('.//div[@class="post-card-inline__meta"]//p[@class="post-card-inline__author"]//a//text()')
author = ''.join(authors).strip()
view_counts = element.xpath('.//div[@class ="post-card-inline__stats"]//span//text()')
if view_counts:
view_count = ''.join(view_counts)
if view_count:
view_count = view_count.replace(" ", "")
view_count = int(view_count)
article = {
'title': title,
'url': url,
'description': description,
'time': time,
'author': author,
'view_count': view_count,
}
articles.append(article)
articles = json.dumps(articles, sort_keys=True, indent=4)
print(articles)
Lưu trữ kết quả bóc tách với pandas
pandas là một thư viện khác với requests, nó thường được sử dùng để làm việc với các file excell hay csv. Trong bài tập này, chúng ta sẽ dùng một file csv để lưu kết quả là danh sách các bài viết từ trang cointelegraph.
CSV (Comma Separated Values) là một loại định dạng văn bản đơn giản mà trong đó, các giá trị được ngăn cách với nhau bằng dấu phẩy. Định dạng CSV thường xuyên được sử dụng để lưu các bảng tính quy mô nhỏ như danh bạ, danh sách lớp, báo cáo…
...
if not articles:
print(f'Cannot found any articles!')
# Creates DataFrame.
df = pd.DataFrame(articles)
file_name = 'articles.csv'
df.to_csv(file_name, index=False)
Kết quả thu thập
Full source code
import requests
from lxml import html
import pandas as pd
def fetch_html_response():
url = "https://cointelegraph.com/tags/bitcoin"
headers = {
"authority": "cointelegraph.com",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-language": "en-US,en;q=0.9,vi;q=0.8",
"cache-control": "no-cache",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
response = requests.request("GET", url, headers=headers)
return response
def extract_articles(response):
tree = html.fromstring(response.content.decode(response.encoding))
article_elements = tree.xpath('//li[@class="posts-listing__item"]//article[@class="post-card-inline"]')
articles = []
for element in article_elements:
title = None
url = None
description = None
time = None
author = None
view_count = None
titles = element.xpath('.//a[@class="post-card-inline__title-link"]//span//text()')
if titles:
title = ''.join(titles)
hrefs = element.xpath('.//a[@class="post-card-inline__title-link"]//@href')
if hrefs:
href = ''.join(hrefs)
url = f'https://cointelegraph.com/{href}'
descriptions = element.xpath('.//p[@class="post-card-inline__text"]//text()')
if descriptions:
description = ''.join(descriptions)
time_agos = element.xpath('.//div[@class="post-card-inline__meta"]//time//text()')
time_ago = ''.join(time_agos)
times = element.xpath('.//div[@class="post-card-inline__meta"]//time//@datetime')
time = ''.join(times)
time = time if 'ago' not in time_ago else f'{time} {time_ago}'
authors = element.xpath('.//div[@class="post-card-inline__meta"]//p[@class="post-card-inline__author"]//a//text()')
author = ''.join(authors).strip()
view_counts = element.xpath('.//div[@class ="post-card-inline__stats"]//span//text()')
if view_counts:
view_count = ''.join(view_counts)
if view_count:
view_count = view_count.replace(" ", "")
view_count = int(view_count)
article = {
'title': title,
'url': url,
'description': description,
'time': time,
'author': author,
'view_count': view_count,
}
articles.append(article)
return articles
if __name__ == '__main__':
html_response = fetch_html_response()
articles = extract_articles(html_response)
if not articles:
print(f'Cannot found any articles!')
else:
# Creates DataFrame.
df = pd.DataFrame(articles)
file_name = 'articles.csv'
df.to_csv(file_name, index=False)
print(f'Saved {len(articles)} articles to {file_name}!')
Các bạn có tải file articles.csv lên Google Drive và mở bằng Google Sheets để xem thành quả của mình.