Crawlee for Python: कतार, डिडुपे, और एक असली क्रॉल/render करें
Senior Web Scraping Engineer
TL;DR:
- Crawlee for Python आपको एक अनुरोध कतार, स्वचालित URL डिडुप्लीकेशन,
enqueue_links(), और एक डेटा सेट लेखक प्रदान करता है, इसलिए एक पृष्ठित क्रॉलर एक हैंडलर फ़ंक्शन है। - Crawlee का स्टोरेज प्रति प्रक्रिया साझा होता है, न कि प्रति क्रॉलर।
purge_on_startTrueहै और फिर भी एक स्क्रिप्ट में दो क्रॉलर को अलग नहीं करता है। - मापी गई: एक स्क्रिप्ट में
max_requests_per_crawl=2के साथ दो समान क्रॉलर। पहले ने 2 अनुरोध पूरे किए और 20 आइटम लिखे; दूसरे ने 3 अनुरोध पूरे किए और 5 पृष्ठों में 50 आइटम की रिपोर्ट की। BeautifulSoupCrawlerजावास्क्रिप्ट नहीं चलाता है। एक क्लाइंट-Rendered पृष्ठ पर यह अनुरोध को समाप्त करता है और 0 आइटम उत्पन्न करता है।- Crawlee का
HttpClientबेस क्लास चार तरीकों में होता है। Scrapeless यूनिवर्सल स्क्रैपिंग API को कॉल करने वाले एक का कार्यान्वयन उसी पृष्ठ से 10 आइटम लौटाता है, जबकि राउटर हैंडलर अपरिवर्तित रहता है। - Scrapeless का मुफ्त प्लान इस गाइड में हर अनुरोध को कवर करता है।
Crawlee for Python एक स्क्रैपर का वह हिस्सा है जिसे आप अन्यथा स्वयं लिखते: वह कतार जो URLs को होल्ड करती है, वह सेट जो आपको एक बार फिर से प्राप्त करने से रोकता है, समवर्तीता सीमित करने वाला, और लेखक जो परिणामों को डिस्क पर डालता है। आप एक हैंडलर प्रदान करते हैं जो एक पार्स किए गए पृष्ठ को प्राप्त करता है।
क्योंकि Crawlee कतार और स्टोरेज का मालिक है, इसके डिफ़ॉल्ट आपके परिणामों को कैसे दिखता है यह तय करते हैं — और इनमें से दो ऐसे संख्याएँ उत्पन्न करते हैं जो गलत हैं जिस तरह से कोई अपवाद आपको उनके बारे में बताने वाला नहीं है।
यह गाइड एक कार्यशील क्रॉलर का निर्माण करती है जो एक सक्रिय साइट पर है, यह मापती है कि स्टोरेज डिफ़ॉल्ट दूसरे क्रॉलर के लिए क्या करता है, फिर परिवहन को स्वैप करती है ताकि वही हैंडलर एक पृष्ठ पर काम करे जो ब्राउज़र में दिखाई देता है।
Crawlee आपको क्या देता है
Crawlee कई क्रॉलर क्लासेज प्रदान करता है जो एक इंटरफेस साझा करते हैं। आप जो चुनते हैं वह यह तय करता है कि पृष्ठ कैसे पार्स किया जाता है:
BeautifulSoupCrawlerऔरParselCrawlerHTTP पर फ़ेच करते हैं और आपके हैंडलर को एक पार्स की गई ट्री देते हैं।HttpCrawlerआपको बिना किसी पार्सिंग के कच्चा प्रतिक्रिया देता है।PlaywrightCrawlerऔरAdaptivePlaywrightCrawlerएक असली ब्राउज़र को चलाते हैं।
इन सभी को एक ही राउटर, एक ही समवर्तीता सेटिंग्स, और एक ही स्टोरेज स्वीकार होता है। इनके बीच स्वैपिंग करने से आपके हैंडलर को प्राप्त होने वाले संदर्भ वस्तु में परिवर्तन होता है, यही वजह है कि HTTP क्रॉलर से ब्राउज़र क्रॉलर में जाना एक वन-लाइन परिवर्तन नहीं होता।
इंस्टॉल
bash
pip install 'crawlee[beautifulsoup]'
अतिरिक्त महत्वपूर्ण है — बेस crawlee पैकेज Beautiful Soup को शामिल नहीं करता है। सत्यापन रन ने crawlee 1.9.0 को beautifulsoup4 4.15.0 के साथ Python 3.12 पर उपयोग किया।
आपका पहला क्रॉलर
एक क्रॉलर एक क्लास और एक सजावट की गई हैंडलर है। हैंडलर एक संदर्भ प्राप्त करता है जो पार्स किया गया पृष्ठ, अनुरोध, और डेटा धकेलने और अधिक URLs को कतार में डालने के तरीके रखता है।
python
def build(*, storage_dir: str | None = None, http_client=None, max_requests: int = 3):
crawler = BeautifulSoupCrawler(
http_client=http_client,
max_requests_per_crawl=max_requests,
concurrency_settings=ConcurrencySettings(desired_concurrency=2, max_concurrency=2),
configuration=Configuration(storage_dir=storage_dir) if storage_dir else None,
)
@crawler.router.default_handler
async def handler(context: BeautifulSoupCrawlingContext) -> None:
for quote in context.soup.select("div.quote"):
await context.push_data({
"text": quote.select_one("span.text").get_text(strip=True),
"author": quote.select_one("small.author").get_text(strip=True),
"url": context.request.url,
})
await context.enqueue_links(selector="li.next a")
return crawler
context.soup एक Beautiful Soup वस्तु है, इसलिए मौजूदा चयनकर्ता अपरिवर्तित रहते हैं। context.push_data() डेटा सेट में जोड़ता है। context.enqueue_links(selector=...) उस चयनकर्ता से मिलते-जुलते एंकर खोजता है, प्रत्येक को वर्तमान पृष्ठ के खिलाफ आधार-URL नियमों का उपयोग करके हल करता है, और परिणामों को कतार में जोड़ता है — पहले से ही डिडुप्लिकेट, इसलिए एक "अगला" लिंक जो एक विजिट किए गए पृष्ठ की ओर इशारा करता है, उसका कोई खर्च नहीं होता।
ConcurrencySettings एक max_concurrency को उसके desired_concurrency से नीचे स्वीकार नहीं करता, इसलिए जब आप इसे कम करते हैं, तो दोनों को सेट करें।
एक सक्रिय उद्धरण साइट के तीन पृष्ठों के खिलाफ चलाएँ:
text
static
requests finished : 3
dataset items : 30
distinct pages : 3
first quote : “The world as we have created it is a process of our think
first author : Albert Einstein
तीन अनुरोध, प्रत्येक में दस उद्धरण, तीन अलग-अलग स्रोत URLs। हैंडलर ने कभी कोई URL नहीं बनाया या विजिट किए गए सेट को ट्रैक नहीं किया।
डेटा कहाँ जाता है
push_data() एक डेटा सेट के तहत ./storage में लिखता है, और crawler.get_data() इसे वापस पढ़ता है:
python
async def report(label, crawler, start_url):
await crawler.run([start_url])
data = await crawler.get_data()
print(f" {label}")
print(f" requests finished : {crawler.statistics.state.requests_finished}")
print(f" dataset items : {data.count}")
print(f" अद्वितीय पृष्ठ : {len({i['url'] for i in data.items})}")
return data
crawler.statistics.state.requests_finished वह संख्या है जो Crawlee वास्तव में पूरी करता है, जो डेटा सेट की गणना के बगल में प्रिंट करने योग्य है। जब ये दोनों आपकी अपेक्षा के साथ असहमत होते हैं, तो कारण आमतौर पर अगली अनुभाग में होता है।
संग्रहण आपके क्रॉलर से अधिक समय तक चलता है
Configuration().purge_on_start True है। इसका अर्थ है कि हर रन खाली डेटा सेट और खाली कतार से शुरू होता है। यह सच नहीं है - सफाई एक बार होती है, जब भंडारण प्रक्रिया में पहली बार खोला जाता है, इसलिए उसी स्क्रिप्ट में बनाया गया दूसरा क्रॉलर पहले वाले द्वारा छोड़े गए भंडारण में शामिल हो जाता है।
दो क्रॉलर, जो उसी फंक्शन द्वारा बनाए गए हैं, दोनों को दो अनुरोधों तक सीमित रखा गया है, और दोनों एक ही URL से शुरू होते हैं:
python
await report("क्रॉलर A", build(max_requests=2), "https://quotes.toscrape.com/")
await report("क्रॉलर B", build(max_requests=2), "https://quotes.toscrape.com/")
text
क्रॉलर A
अनुरोध पूरे हुए : 2
डेटा सेट आइटम : 20
अद्वितीय पृष्ठ : 2
क्रॉलर B
अनुरोध पूरे हुए : 3
डेटा सेट आइटम : 50
अद्वितीय पृष्ठ : 5
क्रॉलर B को दो अनुरोधों के लिए कॉन्फ़िगर किया गया था और उसने तीन पूरे किए। इसका डेटा सेट 5 अद्वितीय पृष्ठों पर 50 आइटम रिपोर्ट करता है, जिसमें सब कुछ शामिल है जो क्रॉलर A ने लिखा था। कुछ भी नहीं उठा, और दोनों रन सफल के रूप में लॉग किए गए।
क्रॉलर B को दिया गया प्रारंभ URL पहले ही विज़िट किया जा चुका था, इसलिए डुप्लिकेशन ने इसे छोड़ दिया, जबकि वह पृष्ठ जो क्रॉलर A ने कतारबद्ध किया था और कभी नहीं पहुँचा, अभी भी प्रतीक्षा कर रहा था। किसी क्रॉलर द्वारा रिपोर्ट किया गया डेटा सेट और सीमा दोनों साझा भंडारण के गुण होते हैं, न कि उस क्रॉलर के।
जब वे एक प्रक्रिया साझा करते हैं, तो प्रत्येक क्रॉलर को अपना भंडारण निर्देशिका दें। यही एक तर्क है जिसे ऊपर के बिल्डर ने इसके लिए लिया है:
python
configuration=Configuration(storage_dir=storage_dir) if storage_dir else None,
storage_dir को क्रॉलर के हिसाब से सेट करके वही तीन चरण फिर से चलाएँ और गणनाएँ आपकी सेटिंग के अनुसार हो जाएँगी। एक प्रक्रिया में एक क्रॉलर दूसरा उत्तर है, और उत्पादन के लिए सरल एक है।
जब पृष्ठ ब्राउज़र में रेंडर होता है
https://quotes.toscrape.com/js/ अपने DOM को एक JavaScript ऐरे से बनाता है। BeautifulSoupCrawler इसे कोई शिकायत किए बिना लाता है:
text
जावास्क्रिप्ट
अनुरोध पूरे हुए : 1
डेटा सेट आइटम : 0
अद्वितीय पृष्ठ : 0
एक अनुरोध पूरा हुआ, शून्य आइटम। मार्कअप जो Crawlee को प्राप्त हुआ है उसमें कोई div.quote तत्व नहीं है, और HTTP क्रॉलर के पास उन्हें बनाने के लिए कुछ भी नहीं है।
दस्तावेजीकृत उत्तर PlaywrightCrawler है, जिसका अर्थ एक ब्राउज़र निर्भरता, एक विभिन्न संदर्भ वस्तु, और हैंडलर के पार्सिंग को फिर से लिखना है। संकीर्ण परिवर्तन BeautifulSoupCrawler को बनाए रखना और केवल इसके परिवहन को बदलना है, जिसे Crawlee http_client पैरामीटर के माध्यम से समर्थन करता है।
HttpClient चार तरीके हैं, और इनमें से केवल दो को असली काम की आवश्यकता है। एक प्रतिक्रिया वस्तु जो Crawlee के HttpResponse संरचनात्मक प्रकार को संतोषजनक बनाती है — पायथन टाइपिंग प्रोटोकॉल स्पेसिफिकेशन के संदर्भ में एक प्रोटोकॉल — रेंडर किए गए HTML को लपेटती है। इसमें एक स्थिति कोड और हेडर को उजागर करना आवश्यक है क्योंकि Crawlee उन्हें HTTP अर्थशास्त्र स्पेसिफिकेशन के तरीके से मानता है:
python
class RenderedResponse:
"""Crawlee के HttpResponse प्रोटोकॉल के लिए रेंडर किए गए HTML स्ट्रिंग को अनुकूलित करता है।"""
def __init__(self, body: bytes, status_code: int = 200) -> None:
self._body = body
self._status_code = status_code
@property
def http_version(self) -> str:
return "HTTP/1.1"
@property
def status_code(self) -> int:
return self._status_code
@property
def headers(self) -> HttpHeaders:
return HttpHeaders({"content-type": "text/html; charset=utf-8"})
async def read(self) -> bytes:
return self._body
async def read_stream(self) -> AsyncIterator[bytes]:
raise RuntimeError("स्ट्रीमिंग इस क्लाइंट द्वारा समर्थित नहीं है")
yield b""
क्लाइंट स्वयं Scrapeless यूनिवर्सल स्क्रेपिंग API को कॉल करता है। वह एंडपॉइंट पृष्ठ को रेंडर करता है और HTML को स्ट्रिंग के रूप में लौटाता है। अवरुद्ध कॉल asyncio.to_thread के माध्यम से गुजरता है ताकि यह उस घटना लूप को फंसाए न, जिसे asyncio कार्य दस्तावेज़ में वर्णित किया गया है:
python
class ScrapelessHttpClient(HttpClient):
"""प्रत्येक Crawlee अनुरोध को यूनीवर्सल स्क्रेपिंग API के माध्यम से रूट करता है।"""
def __init__(self, *, proxy_country: str = "US") -> None:
super().__init__()
self._proxy_country = proxy_country
self._token = os.environ["SCRAPELESS_API_KEY"]
def _render(self, url: str) -> bytes:
Here's the translation of the provided English text into Hindi:
python
payload = {
"actor": "unlocker.webunlocker",
"input": {"url": url, "proxy_country": self._proxy_country, "js_render": True},
}
request = urllib.request.Request(
UNLOCKER,
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json", "x-api-token": self._token},
)
with urllib.request.urlopen(request, timeout=180) as response:
return json.loads(response.read().decode())["data"].encode("utf-8")
async def crawl(self, request, *, session=None, proxy_info=None, statistics=None,
timeout: timedelta | None = None) -> HttpCrawlingResult:
body = await asyncio.to_thread(self._render, request.url)
return HttpCrawlingResult(http_response=RenderedResponse(body))
async def send_request(self, url, *, method="GET", headers=None, payload=None,
session=None, proxy_info=None, timeout=None) -> HttpResponse:
body = await asyncio.to_thread(self._render, url)
return RenderedResponse(body)
def stream(self, url, **kwargs):
raise NotImplementedError("यह क्लाइंट स्ट्रीम नहीं करता है")
async def cleanup(self) -> None:
return None
पृष्ठ को क्रॉल करने के लिए उसी क्रॉलर को पास करें और उसी पृष्ठ को चलाएँ:
text
जावास्क्रिप्ट+एपीआई
रिक्वेस्ट पूरी हुईं : 1
डेटासेट आइटम : 10
विशिष्ट पृष्ठ : 1
पहला उद्धरण : "जिस दुनिया को हमने बनाया है, वह हमारी सोच की एक प्रक्रिया है"
शून्य उत्पादन करने वाले पृष्ठ से दस आइटम। राउटर हैंडलर, चयनक, डेटासेट कॉल और enqueue_links सभी अपरिवर्तित हैं - Crawlee की कतार और डीडुप्लीकेशन काम करते रहते हैं, क्योंकि केवल वह ऑब्जेक्ट जो बाइट्स लाता है, उसे बदल दिया गया। वातावरण में कुंजी को SCRAPELESS_API_KEY के रूप में रखें; यूनिवर्सल स्क्रैपिंग एपीआई स्टार्टिंग गाइड अन्य अनुरोध पैरामीटरों की सूची देता है। यदि आपको डिफ़ॉल्ट HTTP क्लाइंट के लिए प्रॉक्सी राउटिंग की आवश्यकता है, तो Crawlee प्रॉक्सी गाइड उस कॉन्फ़िगरेशन को कवर करता है।
शुरू करने में एक मिनट लगता है - एक मुफ्त Scrapeless खाता बनाएँ और मुफ्त योजना यहां सब कुछ कवर करती है।
इसे चलाएँ
bash
export SCRAPELESS_API_KEY="आपकी-एपीआई-की"
python3 crawlee_demo.py
सत्यापन चलाने से पूरी उत्पादन:
text
crawlee 1.9.0 | beautifulsoup4 4.15.0
purge_on_start डिफ़ॉल्ट: True
--- स्थिर साइट, डिफ़ॉल्ट HTTP क्लाइंट, एकीकृत संग्रह ---
स्थिर
रिक्वेस्ट पूरी हुईं : 3
डेटासेट आइटम : 30
विशिष्ट पृष्ठ : 3
पहला उद्धरण : "जिस दुनिया को हमने बनाया है, वह हमारी सोच की एक प्रक्रिया है"
पहला लेखक : अल्बर्ट आइंस्टीन
--- जावास्क्रिप्ट साइट, डिफ़ॉल्ट HTTP क्लाइंट, एकीकृत संग्रह ---
जावास्क्रिप्ट
रिक्वेस्ट पूरी हुईं : 1
डेटासेट आइटम : 0
विशिष्ट पृष्ठ : 0
--- जावास्क्रिप्ट साइट, ScrapelessHttpClient, एकीकृत संग्रह ---
जावास्क्रिप्ट+एपीआई
रिक्वेस्ट पूरी हुईं : 1
डेटासेट आइटम : 10
विशिष्ट पृष्ठ : 1
पहला उद्धरण : "जिस दुनिया को हमने बनाया है, वह हमारी सोच की एक प्रक्रिया है"
--- दो क्रॉलर, एक प्रक्रिया, डिफ़ॉल्ट संग्रह ---
क्रॉलर A
रिक्वेस्ट पूरी हुईं : 2
डेटासेट आइटम : 20
विशिष्ट पृष्ठ : 2
क्रॉलर B
रिक्वेस्ट पूरी हुईं : 3
डेटासेट आइटम : 50
विशिष्ट पृष्ठ : 5
समस्या निवारण
डेटासेट में इस रन से अधिक आइटम हैं। संग्रह प्रक्रिया के अनुसार साझा किया जाता है। प्रत्येक क्रॉलर के लिए Configuration(storage_dir=...) सेट करें, या रन के बीच ./storage को हटा दें, या एक प्रक्रिया प्रति क्रॉलर चलाएं।
desired_concurrency max_concurrency से अधिक नहीं हो सकता। ConcurrencySettings निर्माण पर जोड़ी को मान्यता देता है। केवल max_concurrency को कम करना चढ़ता है; desired_concurrency को मेल खाने के लिए सेट करें।
ModuleNotFoundError: 'bs4' नाम का कोई मॉड्यूल नहीं है। बेस पैकेज में कोई पार्सर नहीं है। crawlee[beautifulsoup] या crawlee[parsel] स्थापित करें।
HttpHeaders पर ImportError। यह शीर्ष-स्तरीय crawlee पैकेज से निर्यात किया जाता है, उपमॉड्यूल से नहीं।
शून्य आइटम और एक पूरा अनुरोध। पृष्ठ क्लाइंट-साइड पर प्रस्तुत किया गया है। await context.http_response.read() प्रिंट करें और इसे पृष्ठ पर आप जिस मान को देख सकते हैं, उसके लिए खोजें; यदि मान अनुपस्थित है, तो कोई चयनक इसे नहीं खोजेगा।
निष्कर्ष
Crawlee की मूल्यवर्धन आपकी हैंडलर के चारों ओर की मशीनरी है: एक कतार, डीडुप्लीकेशन, सीमित एकत्रता, और एक डेटासेट। वह मशीनरी भी देखने के लिए चीज़ है, क्योंकि यह स्थिति को प्राप्त करती है जो क्रॉलर ऑब्जेक्ट के साथ जीवित रहती है। इस गाइड में दोनों माप इस तथ्य से आते हैं - एक प्रक्रिया में दूसरा क्रॉलर 50 आइटम रिपोर्ट करता है जब उसने बहुत कम हासिल किया, और एक क्लाइंट-संक्षिप्त पृष्ठ एक साफ शून्य लौटाता है।
दोनों एक पंक्ति में निदान योग्य हैं। हर रन पर `requests_finished` को डेटा सेट की गिनती के साथ प्रिंट करें; जब वे आपके कॉन्फ़िगरेशन से असंगत हों, तो चयनकर्ताओं के पहले स्टोरेज को देखें। और जब गिनती शून्य होती है क्योंकि मार्कअप खाली आया है, तो सबसे छोटा समाधान परिवहन को बदलना और हैंडलर को अकेला छोड़ना है।
क्या आप इसे करने के लिए तैयार हैं? <a href="https://app.scrapeless.com/passport/login?utm_source=website&utm_medium=blog&utm_campaign=universalscrapingapi&utm_term=crawlee-python-web-scraping"><strong>Scrapeless मुफ्त योजना के साथ शुरू करें</strong></a> और <a href="https://www.scrapeless.com/hi/pricing?utm_source=website&utm_medium=blog&utm_campaign=universalscrapingapi&utm_term=crawlee-python-web-scraping"><strong>वर्तमान मूल्य निर्धारण</strong></a> उच्च मात्रा के लिए देखें।
## सामान्य प्रश्न
**प्रश्न: मुझे किस Crawlee क्रॉलर क्लास से शुरुआत करनी चाहिए?**
यदि डेटा सेवा किए गए HTML में है, तो `BeautifulSoupCrawler` से शुरू करें, क्योंकि यह प्रति पृष्ठ एक HTTP अनुरोध की लागत करता है और आपको एक परिचित पैर्स किया हुआ पेड़ देता है। यदि आपको XPath पसंद है तो `ParselCrawler` पर जाएं, कच्चे बाइट्स चाहिए तो `HttpCrawler` पर और केवल जब पृष्ठ को वाकई ब्राउज़र की आवश्यकता हो तभी Playwright क्रॉलर पर।
**प्रश्न: Crawlee मेरे लिए लूप स्वयं लिखने से कैसे भिन्न है?**
Crawlee अनुरोध कतार, URL डूप्लिकेशन, निर्धारित समवर्तीता और डेटा सेट निरंतरता प्रदान करता है। उपरोक्त रन में, `enqueue_links(selector="li.next a")` ने हैंडलर द्वारा एक भी URL बनाए बिना तीन पृष्ठों को चलाया।
**प्रश्न: क्यों मेरी डेटा सेट में पिछले रन के परिणाम शामिल हैं?**
क्योंकि Crawlee का स्टोरेज प्रति प्रक्रिया साझा होता है और `purge_on_start` तब एक बार फायर होता है जब स्टोरेज पहले खोला जाता है, न कि प्रति क्रॉलर। एक स्क्रिप्ट में दो क्रॉलर एक डेटा सेट और एक अनुरोध कतार साझा करते हैं। प्रत्येक को `Configuration(storage_dir=...)` दें, या प्रति प्रक्रिया एक क्रॉलर चलाएं।
**प्रश्न: क्या मुझे JavaScript पृष्ठों के लिए PlaywrightCrawler पर स्विच करना होगा?**
नहीं। `PlaywrightCrawler` एक विकल्प है, लेकिन यह क्रॉलर वर्ग और संदर्भ को बदलता है जो आपका हैंडलर प्राप्त करता है। Crawlee के `HttpClient` इंटरफेस को लागू करने से केवल बाइट्स को फ़ेच करने का तरीका बदलता है, यही वजह है कि इस मार्गदर्शिका में हैंडलर बिना संपादन के 0 आइटम से 10 आइटम पर चला गया।
**प्रश्न: Crawlee अपनी आउटपुट कहां लिखता है?**
डिफ़ॉल्ट रूप से `./storage` के अंतर्गत, डेटा सेट `storage/datasets/` में होते हैं। `crawler.get_data()` उसी प्रक्रिया में डेटा सेट को वापस पढ़ता है, और `Configuration(storage_dir=...)` पूरे पेड़ को कहीं और ले जाता है।
स्क्रैपलेस में, हम केवल सार्वजनिक रूप से उपलब्ध डेटा का उपयोग करते हैं, जबकि लागू कानूनों, विनियमों और वेबसाइट गोपनीयता नीतियों का सख्ती से अनुपालन करते हैं। इस ब्लॉग में सामग्री केवल प्रदर्शन उद्देश्यों के लिए है और इसमें कोई अवैध या उल्लंघन करने वाली गतिविधियों को शामिल नहीं किया गया है। हम इस ब्लॉग या तृतीय-पक्ष लिंक से जानकारी के उपयोग के लिए सभी देयता को कोई गारंटी नहीं देते हैं और सभी देयता का खुलासा करते हैं। किसी भी स्क्रैपिंग गतिविधियों में संलग्न होने से पहले, अपने कानूनी सलाहकार से परामर्श करें और लक्ष्य वेबसाइट की सेवा की शर्तों की समीक्षा करें या आवश्यक अनुमतियाँ प्राप्त करें।



