بستهها، REST API و XML
تا اینجا فقط از کتابخانه استاندارد استفاده کردیم. حالا از پایتون بیرون میرویم: بسته نصب میکنیم، به اینترنت وصل میشویم و داده واقعی میگیریم.
۱. چرا محیط مجازی
فرض کنید دو پروژه دارید. یکی با نسخه ۱ یک کتابخانه کار میکند و دیگری با نسخه ۲. اگر همه چیز را روی پایتون سیستم نصب کنید، نمیشود هر دو را داشت. محیط مجازی پوشهای خصوصی است که نسخهای از پایتون و فقط بستههای همین پروژه را در خود دارد.
python -m venv .venv
ساختن محیط مجازی. پوشهای به نام venv. ساخته میشود
# Windows (Command Prompt)
.venv\Scripts\activate
# Windows (PowerShell)
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned
.\.venv\Scripts\activate
# Linux, macOS, Git Bash
source .venv/bin/activate
فعال کردن محیط
بعد از فعال شدن، نام محیط جلوی خط فرمان ظاهر میشود:
(.venv). برای خروج، فقط deactivate بنویسید.
.venv/را به.gitignoreاضافه کنید. محیط مجازی هرگز نباید در گیت ذخیره شود.requirements.txtرا حتماً در گیت بگذارید. این چیزی است که بقیه لازم دارند.- هر پروژه، یک محیط مجازی. همیشه.
۲. دستورهای pip
python -m pip install --upgrade pip
pip install MODULE_NAME
pip install MODULE_NAME==1.2.3 # a precise version
pip list # what is installed
pip show MODULE_NAME # details of one package
pip uninstall MODULE_NAME
دستورهای اصلی pip
pip ابزار نصب بستههای پایتون است و بستهها را از مخزن
جهانی PyPI میگیرد. بیش از پانصد هزار بسته آنجا هست.
۳. فایل requirements.txt
pip freeze > requirements.txt # write down everything that is installed
pip install -r requirements.txt # rebuild the same environment elsewhere
ذخیره و بازسازی محیط
این فایل فهرست دقیق بستهها و نسخههایشان است. هر کسی که پروژه شما را بگیرد، با یک دستور دقیقاً همان محیط را میسازد. این استاندارد جهانی پروژههای پایتون است.
در نسخه اصلی، فایلهای requirements.txt با انکدینگ
UTF-16 ذخیره شده بودند، چون در ویندوز با
> در PowerShell ساخته شده بودند. نتیجه این بود که
pip install -r requirements.txt اصلاً نمیتوانست
بخواندشان. اگر با این مشکل روبهرو شدید، فایل را در VS Code باز
کنید و انکدینگش را به UTF-8 تغییر دهید.
۴. اولین بسته نصبشده
'''A first third-party package: the Dilami (Gilaki) calendar.
Before running:
python -m venv .venv
(activate it)
pip install -r requirements.txt
'''
from datetime import datetime
# A package that is not installed raises ModuleNotFoundError, so we give a
# clear message instead of a traceback.
try:
from dilami_calendar import DilamiDatetime
except ModuleNotFoundError:
raise SystemExit('Run "pip install -r requirements.txt" first.')
def main() -> None:
print('The Dilami date of today:')
print(DilamiDatetime.now())
print('Converting a Gregorian date to Dilami:')
gregorian_datetime = datetime(2026, 7, 19)
dilami_datetime = DilamiDatetime(gregorian_datetime)
print(f' Year: {dilami_datetime.year}')
print(f' Month: {dilami_datetime.month}')
print(f' Day: {dilami_datetime.day}')
تقویم دیلمی: نمونه 01_venv_and_pip
تقویم دیلمی، تقویم بومی گیلان است و بستهاش را یک برنامهنویس ایرانی نوشته است. نمونه خوبی است از اینکه در PyPI برای هر نیازی چیزی پیدا میشود.
آن try دور import عادت خوبی است: به جای
اینکه کاربر یک ردیابی ترسناک ببیند، جمله روشنی میخواند که به او
میگوید چه کار کند. در همه نمونههای این فصل همین الگو به کار
رفته است.
۵. چند بسته محبوب
| بسته | وظیفه آن | پوشه نمونه |
|---|---|---|
matplotlib | رسم نمودار | 02_matplotlib |
requests | کار با وب و وبسرویس | 03_requests_basics |
Pillow | پردازش تصویر | 04_pillow |
pandas | تحلیل داده و جدول | 05_pandas |
این چهار بسته، پرکاربردترینهای دنیای پایتوناند. هر کدام خودشان یک دوره کامل میطلبند؛ اینجا فقط میبینید که وجود دارند و چطور نصب و استفاده میشوند.
۶. وبسرویس REST چیست
API یعنی راهی که یک برنامه از راه اینترنت به برنامه دیگری داده میدهد. سبک REST رایجترین شکلش است و واژگانش این مواردست:
| مفهوم | یعنی |
|---|---|
| URL | کدام منبع را میخواهیم |
| متد | GET خواندن، POST ساختن، PUT تغییر، DELETE حذف |
| کد وضعیت | ۲۰۰ موفق، ۴۰۴ پیدا نشد، ۵۰۰ خطای سرور |
| بدنه | خود داده، تقریباً همیشه JSON |
۷. کتابخانه requests
try:
import requests
except ModuleNotFoundError:
raise SystemExit('Run "pip install -r requirements.txt" first.')
def main() -> None:
# 1. A simple GET, with a timeout so the program cannot hang for ever.
try:
response = requests.get('https://api.github.com', timeout=10)
response.raise_for_status() # raises when the status is 4xx or 5xx
except requests.RequestException as e:
print('The request failed:', e)
return
print('Status code:', response.status_code) # 200 means success
# 2. The answer parsed as JSON -> a Python dictionary.
data = response.json()
print('Example field user_url:', data['current_user_url'])
# 3. Query parameters: requests builds the ?q=python&sort=stars part for us.
params = {'q': 'python', 'sort': 'stars'}
search = requests.get(
'https://api.github.com/search/repositories',
params=params,
timeout=10,
)
search.raise_for_status()
result = search.json()
print('Number of search results:', result['total_count'])
print('The final URL was:', search.url)
for repo in result['items'][:5]:
print(f" {repo['full_name']:<40} {repo['stargazers_count']:>8} stars")
اولین درخواست: نمونه 03_requests_basics
چهار قانون کار با اینترنت
-
همیشه
timeoutبگذارید. بدون آن، اگر سرور جواب ندهد، برنامه شما تا ابد منتظر میماند. این یکی از باگهای نسخه اصلی این دوره بود. -
همیشه
raise_for_status()را درtryبگذارید. بدون گرفتنش، هر خطای شبکه برنامه را میکشد. -
response.json()را کورکورانه صدا نزنید. اگر سرور HTML خطا برگرداند، خطایJSONDecodeErrorمیگیرید. -
پارامترها را با
paramsبدهید، نه با چسباندن رشته. کتابخانه خودش کاراکترهای خاص را درست کدگذاری میکند.
۸. وبسرویس صفحهبندیشده
هیچ وبسرویسی ده هزار رکورد را یکجا نمیدهد. دادهها صفحهبندی میشوند و شما باید صفحه به صفحه بگیریدشان.
def fetch_all_posts(endpoint: str) -> list[dict]:
'Download every page of posts and return them in one list.'
all_posts: list[dict] = []
page = 1
while True:
try:
response = requests.get(
endpoint,
params={'page': page, 'per_page': PER_PAGE},
timeout=TIMEOUT,
)
response.raise_for_status()
except requests.RequestException as e:
print(f'Error fetching page {page}: {e}')
break
posts = response.json()
if not posts: # an empty page means we are done
break
all_posts.extend(posts)
# The API tells us how many pages exist, in a header.
total_pages = int(response.headers.get('X-WP-TotalPages', 1))
if page >= total_pages:
break
page += 1
return all_posts
گرفتن همه صفحهها: نمونه 09_rest_api
شرح مثال
- الگوی
while Trueباbreakاز فصل هشتم، اینجا دقیقاً همان چیزی است که لازم است: نمیدانیم چند صفحه وجود دارد. - دو شرط خروج داریم: صفحه خالی، یا رسیدن به آخرین صفحهای که خود سرور در سرآیند اعلام کرده است. داشتن هر دو، حلقه را در برابر رفتار عجیب سرور امن میکند.
- سرآیندها اطلاعات اضافی پاسخاند.
headers.get(..., 1)با مقدار پیشفرض خوانده شده تا اگر سرآیند نبود، برنامه خراب نشود. - در پاسخ این وبسرویس،
post['title']خودش یک شیء است و متن واقعی در['rendered']است. دادههای واقعی وب همیشه تودرتو هستند؛ مهارت فصل پانزدهم اینجا لازم میشود.
۹. ساختار XML
XML قالب قدیمیتری برای داده ساختاریافته است. هنوز در
خیلی جاها استفاده میشود: RSS، فایلهای آفیس، تنظیمات جاوا، و
فایلهای ui. که در فصل سی و ششم میبینید.
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product id="101" name="Intel Core i7-13700K" manufacturer="Intel">
<tags>
<tag>CPU</tag>
<tag>13th Gen</tag>
</tags>
<categories>
<category>Processor</category>
</categories>
</product>
</products>
ساختار XML: فایل products.xml
قواعد XML:
- حتماً یک عنصر ریشه دارد. دو ریشه یعنی فایل نامعتبر.
- هر تگ باز باید بسته شود.
- عنصر میتواند ویژگی داشته باشد (داخل تگ باز) و متن (بین دو تگ).
- به حروف بزرگ و کوچک حساس است.
| XML | JSON | |
|---|---|---|
| حجم | بیشتر، تگها تکرار میشوند | کمتر |
| خوانایی | متوسط | خوب |
| ویژگی و متن | هر دو دارد | فقط کلید و مقدار |
| امروز کجا | RSS، آفیس، سیستمهای قدیمی | وب و موبایل |
در پوشه 06_xml_basics فایلی به نام
broken.xml هست که عمداً دو ریشه دارد و ماژول XML
نمیتواند بخواندش. کنارش fixed.xml نسخه درستش است.
این همان اشتباهی بود که در فایل اصلی دوره وجود داشت و به جای حذف،
به یک درس تبدیل شد.
۱۰. خواندن XML
'''The useful methods of ElementTree:
ET.parse(file) -> a tree
tree.getroot() -> the root element
element.find(tag) -> the FIRST child with that tag (None when missing)
element.findall(tag)-> a list of every child with that tag
element.get(name) -> the value of an ATTRIBUTE (None when missing)
element.text -> the text between the tags
'''
import xml.etree.ElementTree as ET
def parse_xml_to_list(filename: str) -> list[dict]:
'Read products.xml and return a list of dictionaries.'
tree = ET.parse(filename)
root = tree.getroot()
products: list[dict] = []
for product_elem in root.findall('product'):
product: dict = {}
# The attributes of <product ...>
product['id'] = int(product_elem.get('id'))
product['name'] = product_elem.get('name')
product['manufacturer'] = product_elem.get('manufacturer')
# The nested <tags><tag>...</tag></tags>
# find() returns None when the element is missing, so we must test it.
tags_elem = product_elem.find('tags')
tag_list: list[str] = []
if tags_elem is not None:
for tag in tags_elem.findall('tag'):
tag_list.append(tag.text)
product['tags'] = tag_list
products.append(product)
return products
خواندن XML: نمونه 07_read_xml
متد find اگر چیزی پیدا نکند None
برمیگرداند. حتماً بنویسید if elem is not None: و
نه if elem:. دلیلش عجیب است: یک
عنصر XML بدون فرزند، در شرط نادرست حساب میشود، حتی اگر واقعاً
وجود داشته باشد. این یکی از مواردی است که در فصل بیست و پنجم درباره
تفاوت درستیپذیری و is not None گفتیم.
استراتژی کلی: XML را در همان ابتدا به ساختارهای معمولی پایتون تبدیل کنید. بعد از آن، همه ابزارهایی که بلدید کار میکنند و میتوانید در یک خط به JSON تبدیلش کنید.
۱۱. نوشتن XML
'''Building an XML document with ElementTree.
ET.Element(tag) -> the root
ET.SubElement(parent, tag) -> a child
element.text = '...' -> its text (must be a STRING)
element.set('name', value) -> an attribute
'''
import xml.etree.ElementTree as ET
def news_list_to_xml(news_list: list[dict]) -> ET.ElementTree:
'Turn the list of articles into an XML tree.'
root = ET.Element('news')
for article in news_list:
article_elem = ET.SubElement(root, 'article')
# The id fits better as an attribute than as a child element.
article_elem.set('id', str(article['news_id']))
# .text must always be a string - that is why we call str().
ET.SubElement(article_elem, 'title').text = article['title']
ET.SubElement(article_elem, 'content').text = article['content']
tags_elem = ET.SubElement(article_elem, 'tags')
for tag in article['tags']:
ET.SubElement(tags_elem, 'tag').text = tag
comments_elem = ET.SubElement(article_elem, 'comments')
for comment in article['comments']:
comment_elem = ET.SubElement(comments_elem, 'comment')
ET.SubElement(comment_elem, 'user').text = comment['user']
ET.SubElement(comment_elem, 'likes').text = str(comment['likes'])
return ET.ElementTree(root)
def main() -> None:
tree = news_list_to_xml(create_sample_news())
# The simple modern way (Python 3.9+):
ET.indent(tree, space=' ')
tree.write('news_simple.xml', encoding='utf-8', xml_declaration=True)
نوشتن XML: نمونه 08_write_xml
نکته مهم: text و مقدار ویژگیها حتماً باید رشته باشند. عدد دادن خطا ایجاد میکند، پس همیشه str() بزنید. تابع ET.indent هم از پایتون ۳٫۹ اضافه شده و فایل را خوانا و تورفته میکند؛ پیش از آن باید از minidom استفاده میشد.
۱۲. خواندن خوراک RSS
RSS یک فایل XML است که تقریباً هر سایت خبری و
وبلاگی منتشر میکند، معمولاً در نشانی feed/. این آخرین
مثال، همه چیز این فصل را کنار هم میگذارد.
import json
import xml.etree.ElementTree as ET
import requests
SITE_URL = 'https://kadoosedu.ir'
OUTPUT_FILE = 'rss_posts.json'
def text_of(parent: ET.Element, tag: str) -> str:
'Return the text of a child element, or an empty string when it is missing.'
element = parent.find(tag)
if element is None or element.text is None:
return ''
return element.text.strip()
def parse_rss(xml_content: bytes) -> list[dict]:
'''Parse the XML of an RSS feed.
The structure is always: rss > channel > item*
'''
# fromstring() parses a string/bytes; parse() parses a file.
root = ET.fromstring(xml_content)
channel = root.find('channel')
if channel is None:
raise ValueError('this document is not an RSS feed')
posts: list[dict] = []
for item in channel.findall('item'):
posts.append({
'title': text_of(item, 'title'),
'link': text_of(item, 'link'),
'description': text_of(item, 'description'),
'pubDate': text_of(item, 'pubDate'),
})
return posts
def main() -> None:
feed_url = f'{SITE_URL}/feed/'
try:
response = requests.get(feed_url, timeout=15)
response.raise_for_status()
except requests.RequestException as e:
print(f'Error fetching the RSS feed: {e}')
return
try:
posts = parse_rss(response.content)
except (ET.ParseError, ValueError) as e:
print(f'The feed could not be parsed: {e}')
return
for post in posts[:5]:
print(f" - {post['title']}")
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
json.dump(posts, f, ensure_ascii=False, indent=2)
خواندن RSS: نمونه 10_read_rss
شرح مثال
- سه تکنولوژی در یک برنامه:
requestsبرای دانلود،ElementTreeبرای تجزیه،jsonبرای ذخیره. - تابع کمکی
text_ofتکرار بررسیNoneرا حذف میکند. اگر تگی نبود، رشته خالی میدهد و برنامه ادامه پیدا میکند. ET.fromstringبرای رشته و بایت است وET.parseبرای فایل. چون داده از شبکه آمده، اولی لازم است.- هر دو مرحله جدا محافظت شدهاند: خطای شبکه و خطای تجزیه. دادهای که از اینترنت میآید هرگز قابل اعتماد نیست.
- بررسی
if channel is Noneاز باگ نسخه اصلی میآید که فرض میکرد هر فایل XML حتماً RSS است.
پوشه S28-packages-rest-api-and-xml شامل ده پوشه
نمونه، از 01_venv_and_pip تا
10_read_rss. برای اجرای هر کدام، اول محیط مجازی
بسازید و requirements.txt همان پوشه را نصب کنید.
خلاصه فصل
| ابزار | وظیفه آن |
|---|---|
python -m venv .venv | ساختن محیط مجازی |
activate و deactivate | ورود و خروج از محیط |
pip install list show | مدیریت بستهها |
pip freeze > requirements.txt | ثبت بستههای پروژه |
pip install -r requirements.txt | بازسازی محیط |
requests.get(url, timeout=...) | درخواست وب. timeout اجباری |
raise_for_status() | تبدیل خطای سرور به استثنا |
response.json() | تبدیل پاسخ به شیء پایتون |
params={...} | پارامترهای پرسوجو |
ET.parse و getroot | خواندن XML از فایل |
find findall get text | گشتن در درخت XML |
ET.Element و SubElement | ساختن XML |
ET.indent | تورفته کردن خروجی XML |
| RSS | خوراک XML سایتها: rss سپس channel سپس item |
تمرینهای فصل بیست و هشت
- برای یک پروژه تازه محیط مجازی بسازید،
requestsرا در آن نصب کنید وrequirements.txtتولید کنید. - محیط را پاک کنید، از روی
requirements.txtدوباره بسازیدش و ببینید برنامه هنوز کار میکند. - یک وبسرویس عمومی رایگان صدا بزنید، مثلاً
https://api.github.com/users/<name>، و پنج فیلد از پاسخش را چاپ کنید. - پاسخ همان وبسرویس را در فایل JSON ذخیره کنید.
- با matplotlib نمودار میلهای نمرههای
students.csvفصل قبل را رسم کنید. products.xmlرا بخوانید و فایل XML تازهای بنویسید که فقط کارتهای گرافیک را دارد.products.xmlرا به JSON و دوباره به XML تبدیل کنید و دو فایل XML را مقایسه کنید.- خوراک RSS سایتی که دوست دارید را بخوانید و پنج عنوان آخرش را با تاریخشان چاپ کنید.