نحوه تولید عناوین کیفیت و توضیحات متا به صورت خودکار

این اوقات دشوار از هر زمان دیگری انجام کارهای مؤثرتر در کمتر زمان و با منابع کمتری مهمتر است.

یک کار SEO و کسل کننده و وقت گیر که اغلب مورد غفلت واقع می شود ، نوشتن عناوین قانع کننده و توضیحات متا در مقیاس است.

به ویژه ، وقتی سایت دارای هزاران یا میلیون ها صفحه باشد.

وقتی نمی دانید که آیا این پاداش ارزش آن را دارد ، تلاش می کنید تلاش کنید.

در این ستون یاد می گیرید که چگونه از جدیدترین پیشرفت ها در فهم زبان طبیعی و نسل استفاده کنید تا به طور خودکار عناوین با کیفیت و توضیحات متا تولید شود.

ما به راحتی از صفحات Google به این قابلیت نسل هیجان انگیز دسترسی خواهیم داشت. ما یاد خواهیم گرفت که عملکرد را با حداقل کد پایتون و جاوا اسکریپت پیاده سازی کنیم.

نحوه تولید عناوین با کیفیت & # 038؛ توضیحات متا به صورت خودکار

در اینجا برنامه فنی ما است:

  • ما چندین مدل خلاصه متن هنری اخیر را در Google Colab پیاده سازی و ارزیابی خواهیم کرد
  • ما یکی از مدل های یک عملکرد Google Cloud را ارائه خواهیم داد که می توانیم از برنامه های اسکریپت و برگه های Google به راحتی با آنها تماس بگیریم
  • ما محتوای صفحه را مستقیماً از Google Sheets ضبط می کنیم و آن را با عملکرد سفارشی خلاصه می کنیم
  • ما عناوین تولید شده و توضیحات متا را به عنوان آزمایش در Cloudflare با استفاده از RankSense مستقر خواهیم کرد
  • ما یک عملکرد دیگر Google Cloud ایجاد خواهیم کرد تا نمایه سازی خودکار در بینگ انجام شود

معرفی ترانسفورماتورهای صورت در آغوش گرفتن

Hugging Face Transformers یک کتابخانه محبوب در بین محققان و دست اندرکاران هوش مصنوعی است.

این یک رابط یکپارچه و ساده برای استفاده در آخرین تحقیقات زبان طبیعی را فراهم می کند.

فرقی نمی کند که این تحقیق با استفاده از Tensorflow (چارچوب یادگیری عمیق Google) یا Pytorch (چارچوب فیس بوک) کدگذاری شده است. هر دو بیشتر مورد پذیرش قرار گرفته اند.

در حالی که کتابخانه ترانسفورماتور کد ساده تری ارائه می دهد ، به همان اندازه لودویگ برای کاربران نهایی به اندازه کافی ساده نیست (من لودویگ را در مقالات یادگیری عمیق قبلی پوشش داده ام).

اخیراً با معرفی خطوط لوله ترانسفورماتور تغییر یافت.

خطوط لوله بسیاری از موارد استفاده رایج در پردازش زبان طبیعی را با استفاده از حداقل کد در بر می گیرد.

آنها همچنین نسبت به استفاده از مدل زمینه انعطاف پذیری زیادی را ارائه می دهند.

ما چندین گزینه از خلاصه متن متن هنر را با استفاده از خطوط لوله ترانسفورماتور ارزیابی خواهیم کرد.

ما تعدادی نمونه از این نمونه‌ها را قرض می گیریم.

BART Facebook

مایک لوئیس ، محقق فیس بوک ، هنگام اعلام BART ، خلاصه های انتزاعی واقعاً چشمگیر را از مقاله خود به اشتراک گذاشت.

حال ، می بینیم که تکثیر نتایج کار آنها با استفاده از خط لوله ترانسفورماتور آسان است.

ابتدا ، بیایید کتابخانه را در یک نوت بوک Google Colab جدید نصب کنیم.

حتماً زمان اجرا GPU را انتخاب کنید.

!pip install transformers

بعد ، اجازه دهید کد خط لوله را اضافه کنیم.

from transformers import pipeline

# use bart in pytorch
bart_summarizer = pipeline("summarization")

این متن نمونه ای است که خلاصه می کنیم.

TEXT_TO_SUMMARIZE = """ 
New York (CNN)When Liana Barrientos was 23 years old, she got married in Westchester County, New York. 
A year later, she got married again in Westchester County, but to a different man and without divorcing her first husband. 
Only 18 days after that marriage, she got hitched yet again. Then, Barrientos declared "I do" five more times, sometimes only within two weeks of each other. 
In 2010, she married once more, this time in the Bronx. In an application for a marriage license, she stated it was her "first and only" marriage. 
Barrientos, now 39, is facing two criminal counts of "offering a false instrument for filing in the first degree," referring to her false statements on the 
2010 marriage license application, according to court documents. 
Prosecutors said the marriages were part of an immigration scam. 
On Friday, she pleaded not guilty at State Supreme Court in the Bronx, according to her attorney, Christopher Wright, who declined to comment further. 
After leaving court, Barrientos was arrested and charged with theft of service and criminal trespass for allegedly sneaking into the New York subway through an emergency exit, said Detective 
Annette Markowski, a police spokeswoman. In total, Barrientos has been married 10 times, with nine of her marriages occurring between 1999 and 2002. 
All occurred either in Westchester County, Long Island, New Jersey or the Bronx. She is believed to still be married to four men, and at one time, she was married to eight men at once, prosecutors say. 
Prosecutors said the immigration scam involved some of her husbands, who filed for permanent residence status shortly after the marriages. 
Any divorces happened only after such filings were approved. It was unclear whether any of the men will be prosecuted. 
The case was referred to the Bronx District Attorney's Office by Immigration and Customs Enforcement and the Department of Homeland Security's 
Investigation Division. Seven of the men are from so-called "red-flagged" countries, including Egypt, Turkey, Georgia, Pakistan and Mali. 
Her eighth husband, Rashid Rajput, was deported in 2006 to his native Pakistan after an investigation by the Joint Terrorism Task Force. 
If convicted, Barrientos faces up to four years in prison. Her next court appearance is scheduled for May 18.
"""

در اینجا کد خلاصه و نتیجه خلاصه آمده است:

summary = bart_summarizer(TEXT_TO_SUMMARIZE, min_length=50, max_length=250)
print(summary) #Output: [{'summary_text': 'Liana Barrientos has been married 10 times, sometimes within two weeks of each other. Prosecutors say the marriages were part of an immigration scam. She is believed to still be married to four men, and at one time, she was married to eight at once.'}]

من مشخص کردم خلاصه تولید شده نباید کمتر از 50 نویسه و حداکثر 250 باشد.

مقاله پیشنهادی  نحوه استفاده از کنسول جستجوی Google برای جستجوگرها: یک راهنمای کامل

این برای کنترل نوع نسل بسیار مفید است: عناوین یا توضیحات متا.

حال به کیفیت خلاصه تولید شده دقت کنید و فقط چند خط کد پایتون را تایپ کردیم.

خیلی باحال!

'Liana Barrientos has been married 10 times, sometimes within two weeks of each other. Prosecutors say the marriages were part of an immigration scam. She is believed to still be married to four men, and at one time, she was married to eight at once.'

print(len(summary[0]["summary_text"]))
#Output: 249

T5 Google

حالت دیگر مدل هنر ، ترانسفورماتور انتقال متن به متن یا T5 است.

یکی از موفقیتهای چشمگیر این مدل این است که عملکرد آن واقعاً نزدیک به سطح پایه انسان در صفحه SuperGLUE است.

این نکته قابل توجه است زیرا وظایف NLP در SuperGLUE به گونه ای طراحی شده است که برای انسان آسان باشد اما برای ماشین ها سخت است.

گوگل اخیراً خلاصه ای از مطالب مربوط به مدل را منتشر کرده است برای افرادی که تمایل به یادگیری آن از مقاله تحقیق ندارند.

ایده اصلی آنها این بود که هر ایده محبوب NLP را در یک مجموعه جدید آموزش عظیم که آنها را C4 (Colossal Clean Crawled Corpus) می نامند ، امتحان کنند.

می دانم ، محققان هوش مصنوعی دوست دارند از اختراعات خود نام ببرند.

بیایید از یک لوله ترانسفورماتور دیگر برای خلاصه کردن همان متن استفاده کنیم ، اما این بار با استفاده از T5 به عنوان مدل زمینه ای.

t5_summarizer = pipeline("summarization", model="t5-base", tokenizer="t5-base")

summary = t5_summarizer(TEXT_TO_SUMMARIZE, min_length=50, max_length=250)

در اینجا متن خلاصه آمده است.

[{'summary_text': 'in total, barrientos has been married 10 times, with nine of her marriages occurring between 1999 and 2002 . she is believed to still be married to four men, and at one time, she was married to eight men at once .'}]

این خلاصه همچنین از کیفیت بسیار بالایی برخوردار است.

اما تصمیم گرفتم مدل بزرگتر T5 را که به عنوان خط لوله موجود است نیز امتحان کنم تا ببینم آیا این کیفیت می تواند بهبود یابد یا خیر.

t5_summarizer_larger = pipeline("summarization", model="t5-large", tokenizer="t5-large")

من اصلا نا امید نشده بودم.

خلاصه واقعا چشمگیر!

[{'summary_text': 'Liana barrientos has been married 10 times . nine of her marriages occurred between 1999 and 2002 . she is believed to still be married to four men, and at one time, she was married to eight men at once .'}]

معرفی توابع ابر

اکنون که ما کدی داریم که می تواند محتوای صفحه را به طور خلاصه خلاصه کند ، به یک روش ساده برای افشای آن به عنوان یک API نیاز داریم.

در مقاله قبلی من از Ludwig service برای این کار استفاده کردم ، اما همانطور که ما در اینجا از لودویگ استفاده نمی کنیم ، ما به یک رویکرد متفاوت می رویم: توابع ابر.

عملکردهای ابر و فناوریهای معادل “بدون سرور” ساده ترین روش برای دریافت کد سمت سرور برای استفاده در تولید هستند.

آنها به دلیل عدم نیاز به ارائه سرورهای وب یا ماشین های مجازی در ارائه دهندگان میزبانی وب ، بدون سرور تماس می گیرند.

آنها به طور چشمگیری تجربه استقرار را همانطور که خواهیم دید ساده تر می کنند.

با استفاده از یک عملکرد جهانی ابر سلام

برای به کارگیری اولین عملکرد آزمایشی Cloud نیازی به ترک Google Colab نداریم.

ابتدا به حساب Google Compute خود وارد شوید.

!gcloud auth login --no-launch-browser

سپس ، یک پروژه پیش فرض تنظیم کنید.

!gcloud config set project project-name

در مرحله بعد ، ما عملکرد آزمون خود را در پرونده ای بنام ارسال خواهیم کرد main.py

%%writefile main.py
def hello_get(request):

    """HTTP Cloud Function.

    Args:

        request (flask.Request): The request object.

        

    Returns:

        The response text, or any set of values that can be turned into a

        Response object using `make_response`

        .

    """

    return 'Hello World!'

ما می توانیم با استفاده از این دستور ، این عملکرد را مستقر کنیم.

!gcloud functions deploy hello_get --runtime python37 --trigger-http --allow-unauthenticated

بعد از چند دقیقه ، جزئیات سرویس جدید API ما را می گیریم.

availableMemoryMb: 256
entryPoint: hello_get
httpsTrigger:
url: https://xxx.cloudfunctions.net/hello_get
ingressSettings: ALLOW_ALL
labels:
deployment-tool: cli-gcloud
name: projects/xxx/locations/us-central1/functions/hello_get
runtime: python37
serviceAccountEmail: xxxx
sourceUploadUrl: xxxx
status: ACTIVE
timeout: 60s
updateTime: '2020-04-06T16:33:03.951Z'
versionId: '8'

خودشه!

مقاله پیشنهادی  مولر گوگل در مورد کپی کردن سایت های دارای رتبه برتر

ما نیازی به راه اندازی ماشین های مجازی ، نرم افزار سرور وب و غیره نداریم.

ما می توانیم آن را با باز کردن URL ارائه شده و متن دریافت “سلام جهان!” تست کنیم. به عنوان پاسخ در مرورگر.

با استفاده از عملکرد ابر خلاصه متن ما

در تئوری ، ما باید بتوانیم خط لوله جمع بندی متن خود را به یک تابع بپیچانیم و همان مراحل را برای استقرار یک سرویس API دنبال کنیم.

با این حال ، برای رسیدن به این کار ، مجبور شدم چندین چالش را پشت سر بگذارم.

اولین و چالش برانگیزترین مشکل ما نصب کتابخانه ترانسفورماتور برای شروع بود.

خوشبختانه نصب بسته های شخص ثالث برای استفاده در توابع ابر مبتنی بر پایتون بسیار ساده است.

شما فقط باید یک فایل استاندارد.txt استاندارد مانند این را ایجاد کنید:

%%writefile requirements.txt

transformers==2.0.7

متأسفانه ، این امر به دلیل شکستن ترانسفورماتورها به Pytorch یا Tensorflow انجام می شود. هر دو به طور پیش فرض در Google Colab نصب شده اند ، اما برای محیط Cloud Functions باید مشخص شوند.

به طور پیش فرض ، ترانسفورماتور از Pytorch استفاده می کند ، و هنگامی که من آن را به عنوان الزامات اضافه کردم ، خطایی را ایجاد کرد که من را به این موضوع مفید Stack Overflow سوق داد.

من این فایل را با این پرونده الزام آور.txt به روز شده کار کردم.

%%writefile requirements.txt


https://download.pytorch.org/whl/cpu/torch-1.0.1.post2-cp37-cp37m-linux_x86_64.whl
transformers==2.0.7

چالش بعدی نیاز حافظه عظیم مدل ها و محدودیت های عملکرد Cloud بود.

من ابتدا توابع را با استفاده از خطوط لوله ساده تر مانند NER یکی آزمایش کردم ، NER مخفف Name Entity Recognition است.

من آن را ابتدا در نوت بوک Colab تست می کنم.

from transformers import pipeline

nlp_token_class = None

def ner_get(request):

  global nlp_token_class
  #run once
  if nlp_token_class is None:
    nlp_token_class = pipeline('ner')

  result = nlp_token_class('Hugging Face is a French company based in New-York.')

  return result

من این پاسخ گسست رو گرفتم

[{'entity': 'I-ORG', 'score': 0.9970937967300415, 'word': 'Hu'},
{'entity': 'I-ORG', 'score': 0.9345749020576477, 'word': '##gging'},
{'entity': 'I-ORG', 'score': 0.9787060022354126, 'word': 'Face'},
{'entity': 'I-MISC', 'score': 0.9981995820999146, 'word': 'French'},
{'entity': 'I-LOC', 'score': 0.9983047246932983, 'word': 'New'},
{'entity': 'I-LOC', 'score': 0.8913459181785583, 'word': '-'},
{'entity': 'I-LOC', 'score': 0.9979523420333862, 'word': 'York'}]

سپس ، من به سادگی می توانم اضافه کنم ٪٪ writefile main.py برای ایجاد یک پرونده Python می توانم برای استقرار عملکرد استفاده کنم.

وقتی گزارش ها را مرور کردم تا بدانم چرا تماس های API انجام نشد ، دیدم نیاز حافظه مسئله بزرگی است.

اما ، خوشبختانه ، شما به راحتی می توانید حد مجاز پیش فرض 250M و زمان اجرای آن را با استفاده از این دستور نادیده بگیرید.

!gcloud functions deploy ner_get --memory 2GiB --timeout 540 --runtime python37 --trigger-http --allow-unauthenticated

من اساساً حداکثر حافظه 2 گیگابایتی و 9 دقیقه زمان اجرای را مشخص می کنم تا بارگیری اولیه مدل که می تواند گیگابایت انتقال باشد فراهم می کند.

یک ترفندی که من برای سرعت بخشیدن به تماسهای بعدی به همان عملکرد Cloud استفاده می کنم ، ذخیره کردن مدل بارگیری شده در حافظه با استفاده از یک متغیر جهانی و بررسی اینکه آیا قبل از بازآفرینی خط لوله وجود دارد یا خیر.

پس از آزمایش توابع BART و T5 و با یک مدل کوچک T5 که به خوبی در حافظه و نیازهای زمان عملکرد توابع ابر قرار می گیرد ، مستقر شد.

در اینجا کد آن عملکرد است.

%%writefile main.py

from transformers import pipeline

nlp_t5 = None

def t5_get(request):

  global nlp_t5

  #run once
  if nlp_t5 is None:
    nlp_t5 = pipeline('summarization', model="t5-small", tokenizer="t5-small")

  TEXT_TO_SUMMARIZE = """ 
  New York (CNN)When Liana Barrientos was 23 years old, she got married in Westchester County, New York... 
  """


  result = nlp_t5(TEXT_TO_SUMMARIZE)

  return result[0]["summary_text"]

و در اینجا کد برای استقرار آن وجود دارد.

!gcloud functions deploy t5_get --memory 2GiB --timeout 540 --runtime python37 --trigger-http --allow-unauthenticated

یکی از مشکلات این عملکرد این است که متن به طور خلاصه کدگذاری سخت است.

اما می توانیم با تغییرات زیر به راحتی آن را برطرف کنیم.

%%writefile main.py

from transformers import pipeline

nlp_t5 = None

def t5_post(request):

  global nlp_t5

  #run once

  if nlp_t5 is None:

    #small model to avoid memory issue

    nlp_t5 = pipeline('summarization', model="t5-small", tokenizer="t5-small")

  #Get text to summarize from POST request

  content_type = request.headers['content-type']

  if content_type == 'application/x-www-form-urlencoded':




      text = request.form.get('text')

      result = nlp_t5(text)

      return result[0]["summary_text"]

  else:

      raise ValueError("Unknown content type: {}".format(content_type))

  return "Failure"

من فقط اطمینان می دهم که نوع محتوا به فرم url-encoded شده است و پارامتر را می خوانید متن از داده های فرم

با استفاده از کد زیر می توانم به راحتی این عملکرد را در Colab تست کنم.

import requests
url = "https://us-central1-seo-sheets.cloudfunctions.net/hello_post"
data = {"text": text[:100]}

requests.post(url, data).text

همانطور که همه چیز مطابق آنچه انتظار می رود ، می توانم کار کنم تا بتوانم در Google Sheets کار کنم.

با استفاده از سرویس خلاصه متن از Google Sheets تماس می گیرید

من برنامه ستون اسکریپت را در ستون قبلی خود معرفی کردم و واقعاً به Google Sheets دارای قدرت فوق العاده ای است.

من توانستم تغییرات جزئی را در عملکردی که ایجاد کردم ایجاد کنم تا بتوانم از آن برای خلاصه کردن متن استفاده کنم.

function getSummary(text){
  
  payload = `text=${text}`;
  
  payload = encodeURI(payload);
  
  console.log(payload);
  
  var url = "https://xxx.cloudfunctions.net/t5_post";
  
   var options = {
    "method" : "POST",
    "contentType" : "application/x-www-form-urlencoded",
    "payload" : payload,
    'muteHttpExceptions': true 
  };
        
  var response = UrlFetchApp.fetch(url, options);
  
  var result = response.getContentText();
  
  console.log(result);
  
  return result;
  
}

این همه

مقاله پیشنهادی  Google دکمه "خرید در Google" را برای خرده فروشان رایگان می سازد

نام متغیر ورودی و URL API را تغییر دادم.

بقیه همان چیزی است که من نیاز به ارسال درخواست POST با داده های فرم دارم.

ما تست ها را انجام می دهیم و سیاهه های کنسول را بررسی می کنیم تا مطمئن شویم که همه چیز مطابق آنچه انتظار می رود کار می کند.

این کار را می کند

یک محدودیت بزرگ در برنامه های اسکریپت این است که عملکردهای سفارشی بیش از 30 ثانیه نمی توانند اجرا شوند.

در عمل ، این بدان معنی است که می توانم متن را اگر کمتر از 1200 کاراکتر باشد خلاصه کنم ، در حالی که در Colab / Python مقالات کامل را با بیش از 10،000 کاراکتر تست کردم.

یک روش جایگزین که باید برای متن طولانی تر بهتر کار کند ، به روزرسانی صفحه Google از کد پایتون است همانطور که در این مقاله انجام داده ام.

محتوای صفحه را از صفحات Google حذف کنید

در اینجا چند نمونه از کد کار کامل در Sheets آورده شده است.

نحوه تولید عناوین با کیفیت & # 038؛ توضیحات متا به صورت خودکار

اکنون که می توانیم محتوای متن را خلاصه کنیم ، ببینیم چگونه می توانیم مستقیماً آن را از صفحات وب عمومی استخراج کنیم.

Google Sheets شامل عملکردی قدرتمند برای این کار به نام IMPORTXML است.

ما فقط باید URL و انتخابگر XPath را تهیه کنیم که محتوای مورد نظر برای استخراج را مشخص کند.

در اینجا کد برای استخراج محتوا از صفحه ویکی پدیا آمده است.

=IMPORTXML("https://en.wikipedia.org/wiki/Moon_landing", "//div/text()")

من برای انتخاب همه متن های داخل DIV از یک انتخابگر عمومی استفاده کردم.

با انتخاب های مختلف متناسب با محتوای صفحه هدف خود راحت باشید.

در حالی که ما می توانیم محتوای صفحه را بدست آوریم ، در چندین ردیف تجزیه می شود. ما می توانیم آن را با عملکرد دیگری ، TEXTJOIN اصلاح کنیم.

=TEXTJOIN(" ", TRUE, IMPORTXML("https://en.wikipedia.org/wiki/Moon_landing", "//div/text()"))

سریع فهرست کردن شرح ها و عناوین متا جدید ما در بینگ

بنابراین ، چگونه می دانیم که آیا این قطعات جستجوی جدید عملکرد بهتری نسبت به نمونه های دستی دارند یا در مقایسه با هیچ داده متا به هیچ وجه مقایسه نمی شوند؟

یک روش مطمئن برای یادگیری ، اجرای یک تست زنده است.

در چنین حالتی ، بسیار مهم است که سریع تغییرات خود را ایندکس کنیم.

من با خودکار کردن ابزار بازرسی URL ، نحوه انجام این کار را در Google پوشش داده ام.

این رویکرد ما را به چند صد صفحه محدود می کند.

یک جایگزین بهتر استفاده از API ایندیپینگ سریع بینگ سریع است زیرا ما باید درخواست کنیم تا حداکثر 10،000 آدرس اینترنتی نمایه سازی کنیم!

چقدر باحاله؟

همانطور که در درجه اول علاقه مند به اندازه گیری CTR خواهیم بود ، فرض ما این است که اگر CTR بالاتری را در بینگ دریافت کنیم ، احتمالاً در گوگل و سایر موتورهای جستجو نیز همین اتفاق خواهد افتاد.

من باید با راجر موافق باشم. در اینجا کد برای انجام این کار آمده است.

api_key = "xxx" # Get your own API key from this URL https://www.bing.com/webmaster/home/api

import requests

def submit_to_bing(request):

  global api_key

  api_url=f"https://ssl.bing.com/webmaster/api.svc/json/SubmitUrlbatch?apikey={api_key}"

  print(api_url)

  #Replace for your site 

  url_list = [
      "https://www.domain.com/page1.html",  "https://www.domain.com/page2.html"]

  data = {

    "siteUrl": "http://www.domain.com",

    "urlList": url_list

    }

  r = requests.post(api_url, json=data)

  if r.status_code == 200:

    return r.json()

  else:

    return r.status_code

در صورت موفقیت آمیز بودن ارسال ، انتظار این پاسخ را دارید.

{'d': None}

سپس می توانیم با اضافه کردن آن به ما عملکرد Cloud دیگری ایجاد کنیم main.py مانند گذشته پرونده را استفاده کرده و از آن استفاده کنید.

آزمایش قطعات تولید شده ما در Cloudflare

سرانجام ، اگر سایت شما از Cloudflare CDN استفاده می کند ، می توانید از برنامه RankSense برای اجرای این تغییرات به عنوان آزمایش استفاده کنید ، قبل از اینکه آنها را به سایت منتقل کنید.

به سادگی ستون های خلاصه URL و متن را در یک صفحه Google جدید کپی کرده و آن را در ابزار وارد کنید.

نحوه تولید عناوین با کیفیت & # 038؛ توضیحات متا به صورت خودکار

هنگامی که آزمایش را منتشر می کنید ، می توانید برنامه را برای تغییر تغییر دهید و آدرس اینترنتی وب را تعیین کنید.

یک URL اینترنتی به وب اجازه می دهد تا برنامه ها و سرویس ها در جریان کار خودکار با یکدیگر گفتگو کنند.

نحوه تولید عناوین با کیفیت & # 038؛ توضیحات متا به صورت خودکار

نشانی اینترنتی عملکرد Cloud indexing Bing را کپی و جایگذاری کنید. RankSense پس از انتشار تغییرات در Cloudflare ، 15 دقیقه بصورت خودکار صدا خواهد کرد.

منابعی برای کسب اطلاعات بیشتر

در اینجا پیوندهایی به برخی منابع وجود دارد که در طول تحقیق برای این قطعه واقعاً مفید بودند.


اعتبار تصویر

تمام تصاویر گرفته شده توسط نویسنده ، آوریل 2020