Google Sheets یک ابزار عالی برای استفاده از هوش تجاری و آنالیز داده است. اگر میخواهید وارد کردن دستی دادهها را حذف کنید و در زمان خود صرفهجویی کنید، اجازه دهید به شما نشان دهم که چگونه میتوانید به صورت خودکار دادهها را از منابع خارجی به Google Sheets متصل کرده و وارد کنید.
3 تا از بهترین راهها برای وارد کردن دیتا به Google Sheets عبارت اند از:
- توابع Google Sheets
- افزونههای Google Sheets
- اسکریپت Google Apps
بیایید هر کدام از این 3 روش را با جزئیات بیشتر بررسی کنیم.
1- توابع Google Sheets
Google Sheets دارای 5 تابع داخلی است که میتوانند در وارد کردن دادهها به شما کمک کنند:
- IMPORTDATA
- IMPORTFEED
- IMPORTHTML
- IMPORTRANGE
- IMPORTXML
بیایید ببینیم چگونه میتوانیم از هر یک از این توابع Google Sheets در وارد کردن خودکار دادههای خارجی استفاده کنیم.
1(الف)- ImportData
شما میتوانید از تابع =IMPORTDATA برای کشیدن دادهها از یک URL داده شده در فرمت CSV یا TSV استفاده کنید. تنها لازم است که از تابع =IMPORTDATA(“X”) استفاده کنید و X را با یک URL جایگزین کنید. URL باید در بین دو علامت ” قرار داده شود یا میتواند اشارهای به سلولی باشد که حاوی متن مناسب است. شما در هر Google Sheet ، حداکثر میتوانید از 50 تابع ImportData استفاده کنید.
IMPORTDATA Syntax
IMPORTDATA(url)
نمونههای IMPORTDATA
- IMPORTDATA(“http://www.census.gov/2019census/csv/pop_change.csv”)
- IMPORTDATA(A1)
1(ب)- ImportFeed
برای کشیدن فید RSS یا ATOM از =ImportFeed استفاده کنید.
IMPORTFEED Syntax
IMPORTFEED(url, [query], [headers], [num_items])
url: URL فید RSS یا ATOM ، شامل پروتکل (به عنوان مثال http:// یا https:// ). url باید در گیومه باشد یا میتواند به سلولی اشاره کند که حاوی متن مناسب است.
headers: شامل header های ستون به عنوان یک ردیف اضافی در بالای مقدار برگشتی است. حالت دیفالت غلط است.
num_items: برای query هایی از آیتمهای خاص، شماره مواردی که باید برگردانده شوند، از جدیدترین آنها شروع میشود. اگر num_items مشخص نشده باشند، تعیین این امر اختیاری است. اگر num_items مشخص نشده باشد، تمام مواردی که در حال حاضر در فید منتشر شدهاند، کشیده میشوند.
query: مشخص میکند که چه دادههایی از url واکشی یا fetch شوند. این کار اختیاری است، زیرا دیفالت آن “items” است.
query options:
“feed” یک ردیف را میکشد که حاوی اطلاعات فید از جمله عنوان، توضیحات و URL است.
“feed <type>” یک ویژگی خاص از فید را میکشد. جایی که <type> میتواند عنوان توضیحات، author یا URL باشد.
“items” یک جدول کامل از اطلاعات را که حاوی مواردی از فید است، میکشد. اگر num_items مشخص نشده باشد، تمام مواردی که در حال حاضر در فید منتشر شدهاند، کشیده میشوند.
“items <type>” یک ویژگی از آیتم درخواستی را میکشد. <type> عنوان، خلاصه محتوا، url یا created (تاریخ پست کردن item ) است.
نمونههای IMPORTFEED
- IMPORTFEED(“http://news.google.com/?output=atom”)
- IMPORTFEED(A1,B1,C1,D1)
1 (ج)- ImportHTML
از تابع =ImportHTML برای کشیدن جدول یا لیست دادهها از یک صفحه HTML استفاده کنید. تنها لازم است که از تابع =ImportHTML(X) استفاده کنید و “X” را با URL، query یا index جایگزین کنید.
IMPORTHTML Syntax
IMPORTHTML(url, query, index)
url: URL صفحهای که میخواهید اطلاعات را از آن استخراج کنید ،از جمله پروتکل (به عنوان مثال http:// یا https:// ). url باید در گیومه قرار داده شود یا میتواند اشاره به سلولی باشد که حاوی متن مناسب است.
query: بسته به ساختاری که حاوی دادههایی است که میخواهید بکشید، میتوانید از “list” یا “table” استفاده کنید.
Index: این index ی است که با 1 شروع میشود و جدول یا لیستی که در سورس HTML ی که شما میخواهید بکشید تعریف شده است را مشخص میکند.
نمونههای IMPORTHTML
- IMPORTHTML(“http://en.wikipedia.org/wiki/Demographics_of_India”,”table”,4)
- IMPORTHTML(A2,B2,C2)
1(د)- ImportRange
از تابع =ImportRange برای کشیدن محدودهای از سلولها از یک spreadsheet خاص استفاده کنید. این تابع مستلزم آن است که برگه مجوز وارد کردن داده از spreadsheet دیگر را داشته باشد. هنگامی که برای اولین بار از این تابع استفاده میکنید، از شما خواسته میشود مجوز را فعال کنید. هنگامی که به شما اجازه دسترسی داده شد، هر کاربر در sheet مقصد می تواند از تابع IMPORTRANGE برای وارد کردن دادهها به spreadsheet منبع استفاده کند. هر زمان که بخواهید می توانید مجوز را لغو کنید. همچنین، اگر مجموعه دادهای که میخواهید وارد کنید بسیار بزرگ باشد، یک پیام error دریافت خواهید کرد.
IMPORTRANGE Syntax
IMPORTRANGE(spreadsheet_url, range_string)
spreadsheet_url: این URL مربوط به یک spreadsheet است که میخواهید دادهها را از آن وارد کنید. مقدار spreadsheet_url باید در گیومه باشد یا میتواند اشاره به سلولی باشد که حاوی URL یک spreadsheet است.
range_string: این string یا رشتهای است که به صورت “[sheet_name!]range” قالب بندی شدهاست، و محدودهای را برای کشیدن مشخص میکند. sheet_name range_string اختیاری است. IMPORTRANGE پیشفرض از محدوده دادهشده در صفحه اول خارج میشود. مقدار range_string یا باید در گیومه باشد یا می تواند اشاره به سلولی باشد که حاوی متن مناسب است.
نمونههای IMPORTRANGE
- IMPORTRANGE(“https://docs.google.com/spreadsheets/d/abc123xyz456”, “sheet2!A2:C11”)
- IMPORTRANGE(A1,”B6″)
1(ه)- IMPORTXML
از =ImportXML برای استخراج دادهها از انواع دادههای ساختاریافته مانند XML، HTML، CSV، TSV، RSS یا ATOM استفاده کنید.
IMPORTXML Syntax
IMPORTXML(url, xpath_query)
url: این URL صفحه وب است که باید شامل پروتکل باشد (مانند http:// یا https://). مقدار url یا باید در گیومه باشد یا میتواند اشارهای به سلولی باشد که حاوی متن مناسب است.
xpath_query: XPath query روی دادههای ساختاریافته اجرا میشود.
نمونههای IMPORTXML
- IMPORTXML(“https://en.wikipedia.org/wiki/Apollo_11”, “//a/@href”)
- IMPORTXML(A1,B3)
2- افزونه های Google Sheets
چندین افزونه Google Sheets (معروف به برنامههای Google Sheets) در بازار Google Workspace موجود است که میتوانید از آنها برای اتصال انواع مختلف منابع داده خارجی استفاده کنید. از محبوبترین افزونهها برای اتصال یک منبع داده به Google Sheets، Apipheny و Supermetrics هستند.
Apipheny
Apipheny.io یک افزونه Google Sheets است که میتوانید از آن برای اتصال هر JSON یا CSV API به Google Sheets استفاده کنید. با Apipheny میتوانید تنها با وارد کردن URL و کلید API خود و سپس کلیک کردن بر روی Run، یک API را به Google Sheets وارد کنید.
این یک ابزار no-code ساده برای اتصال API به Google Sheets است. میتوانید دادههای API را از هر منبع داده JSON یا CSV API بیرون بکشید و میتوانید درخواست API خود را ذخیره کنید یا برنامهریزی کنید که به صورت مکرر اجرا شود.
این برنامه با درخواستهای GET و POST کار میکند و همچنین شامل ویژگیهای پیشرفتهای مانند توانایی ذخیره درخواستهای API برای دسترسی آسان و امکان زمانبندی درخواست API برای بازخوانی دادهها در spreadsheet شما به صورت مکرر است.
همچنین یک تابع سفارشی =APIPHENY() وجود دارد که می توانید از آن برای فراخوانی یک درخواست API در spreadsheet خود استفاده کنید یا می توانید به value یک سلول در API query خود اشاره کنید.
برای نصب Apipheny اینجا را کلیک کنید. همچنین میتوانید برای بازدید از وب سایت و کسب اطلاعات بیشتر اینجا را کلیک کنید.
نکته: نویسنده این مقاله یکی از بنیانگذاران Apipheny است!
Supermetrics
Supermetrics یک ابزار اتوماسیون گزارش و بازاریابی است که می توانید از آن برای جذب تبلیغات پولی، سئو، شبکههای اجتماعی و سایر دادههای تحلیلی استفاده کنید. این ابزار دارای تعداد زیادی اتصالدهنده از پیشساختهشده داده است که به شما کمک میکند منابع داده مختلف را به هم متصل کنید تا بتوانید به راحتی دادههای خود را به Google Sheets بکشید.
Supermetrics در مقایسه با Apipheny
دو تفاوت اساسی بین Apipheny و Supermetrics وجود دارد:
ویژگی ها: Supermetrics یک راه حل یکپارچهسازی و گزارشدهی all-in-one داده است که به شما کمک میکند همه چیز را، از اتصال منبع داده گرفته تا ارائه template های گزارش انجام دهید تا بتوانید دادههای خود را به همراه بسیاری از ویژگیهای دیگر visualize کنید. Apipheny یک connector داده DIY API است، شما باید منابع داده API خود را تنظیم و متصل کنید و گزارشهای خود را با دادهها ایجاد کنید.
قیمت: هزینه Supermetrics از 69 دلار در ماه شروع می شود در حالی که Apipheny از 8 دلار در ماه شروع می شود.
برای بازدید از وب سایت سوپرمتریکس اینجا را کلیک کنید.
3- Google Apps Script
میتوانید از Google Apps Script برای نوشتن یک کد ساده استفاده کنید که دادهها را به Google Sheets متصل و واردکند، یا میتوانید یکی از اسکریپتهای اشتراکگذاری شده عمومی را از سایتی مانند Github یا Stack Overflow کپی پیست کنید.
برای مثال، در اینجا نحوه استفاده از Google Apps Script برای وارد کردن دادههای JSON به Google Sheets آورده شده است:
- یک Google Sheet که از قبل موجود است را باز کنید یا یک برگه جدید ایجاد کنید.
- روی “Tools” > “Script Editor” در منو کلیک کنید تا Script editor مربوط بهGoogle Apps باز شود.
- تمام محتوای متغیرهایی را که در Script editor وجود دارد حذف کنید تا بتوانید اسکریپت خود را وارد کنید.
- یک اسکریپت را از Github یا Stack Overflow کپی و جایگذاری کنید.
/**
* Retrieves all the rows in the active spreadsheet that contain data and logs the
* values for each row.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
Logger.log(row);
}
};
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the readRows() function specified above.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Read Data",
functionName : "readRows"
}];
sheet.addMenu("Script Center Menu", entries);
};
/*====================================================================================================================================*
ImportJSON by Trevor Lohrbeer (@FastFedora)
====================================================================================================================================
Version: 1.1
Project Page: http://blog.fastfedora.com/projects/import-json
Copyright: (c) 2012 by Trevor Lohrbeer
License: GNU General Public License, version 3 (GPL-3.0)
http://www.opensource.org/licenses/gpl-3.0.html
------------------------------------------------------------------------------------------------------------------------------------
A library for importing JSON feeds into Google spreadsheets. Functions include:
ImportJSON For use by end users to import a JSON feed from a URL
ImportJSONAdvanced For use by script developers to easily extend the functionality of this library
Future enhancements may include:
- Support for a real XPath like syntax similar to ImportXML for the query parameter
- Support for OAuth authenticated APIs
Or feel free to write these and add on to the library yourself!
------------------------------------------------------------------------------------------------------------------------------------
Changelog:
1.1 Added support for the noHeaders option
1.0 Initial release
*====================================================================================================================================*/
/**
* Imports a JSON feed and returns the results to be inserted into a Google Spreadsheet. The JSON feed is flattened to create
* a two-dimensional array. The first row contains the headers, with each column header indicating the path to that data in
* the JSON feed. The remaining rows contain the data.
*
* By default, data gets transformed so it looks more like a normal data import. Specifically:
*
* - Data from parent JSON elements gets inherited to their child elements, so rows representing child elements contain the values
* of the rows representing their parent elements.
* - Values longer than 256 characters get truncated.
* - Headers have slashes converted to spaces, common prefixes removed and the resulting text converted to title case.
*
* To change this behavior, pass in one of these values in the options parameter:
*
* noInherit: Don't inherit values from parent elements
* noTruncate: Don't truncate values
* rawHeaders: Don't prettify headers
* noHeaders: Don't include headers, only the data
* debugLocation: Prepend each value with the row & column it belongs in
*
* For example:
*
* =ImportJSON("http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=json", "/feed/entry/title,/feed/entry/content",
* "noInherit,noTruncate,rawHeaders")
*
* @param {url} the URL to a public JSON feed
* @param {query} a comma-separated lists of paths to import. Any path starting with one of these paths gets imported.
* @param {options} a comma-separated list of options that alter processing of the data
*
* @return a two-dimensional array containing the data, with the first row containing headers
* @customfunction
**/
function ImportJSON(url, query, options) {
return ImportJSONAdvanced(url, query, options, includeXPath_, defaultTransform_);
}
/**
* An advanced version of ImportJSON designed to be easily extended by a script. This version cannot be called from within a
* spreadsheet.
*
* Imports a JSON feed and returns the results to be inserted into a Google Spreadsheet. The JSON feed is flattened to create
* a two-dimensional array. The first row contains the headers, with each column header indicating the path to that data in
* the JSON feed. The remaining rows contain the data.
*
* Use the include and transformation functions to determine what to include in the import and how to transform the data after it is
* imported.
*
* For example:
*
* =ImportJSON("http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=json",
* "/feed/entry",
* function (query, path) { return path.indexOf(query) == 0; },
* function (data, row, column) { data[row][column] = data[row][column].toString().substr(0, 100); } )
*
* In this example, the import function checks to see if the path to the data being imported starts with the query. The transform
* function takes the data and truncates it. For more robust versions of these functions, see the internal code of this library.
*
* @param {url} the URL to a public JSON feed
* @param {query} the query passed to the include function
* @param {options} a comma-separated list of options that may alter processing of the data
* @param {includeFunc} a function with the signature func(query, path, options) that returns true if the data element at the given path
* should be included or false otherwise.
* @param {transformFunc} a function with the signature func(data, row, column, options) where data is a 2-dimensional array of the data
* and row & column are the current row and column being processed. Any return value is ignored. Note that row 0
* contains the headers for the data, so test for row==0 to process headers only.
*
* @return a two-dimensional array containing the data, with the first row containing headers
**/
function ImportJSONAdvanced(url, query, options, includeFunc, transformFunc) {
var jsondata = UrlFetchApp.fetch(url);
var object = JSON.parse(jsondata.getContentText());
return parseJSONObject_(object, query, options, includeFunc, transformFunc);
}
/**
* Encodes the given value to use within a URL.
*
* @param {value} the value to be encoded
*
* @return the value encoded using URL percent-encoding
*/
function URLEncode(value) {
return encodeURIComponent(value.toString());
}
/**
* Parses a JSON object and returns a two-dimensional array containing the data of that object.
*/
function parseJSONObject_(object, query, options, includeFunc, transformFunc) {
var headers = new Array();
var data = new Array();
if (query && !Array.isArray(query) && query.toString().indexOf(",") != -1) {
query = query.toString().split(",");
}
if (options) {
options = options.toString().split(",");
}
parseData_(headers, data, "", 1, object, query, options, includeFunc);
parseHeaders_(headers, data);
transformData_(data, options, transformFunc);
return hasOption_(options, "noHeaders") ? (data.length > 1 ? data.slice(1) : new Array()) : data;
}
/**
* Parses the data contained within the given value and inserts it into the data two-dimensional array starting at the rowIndex.
* If the data is to be inserted into a new column, a new header is added to the headers array. The value can be an object,
* array or scalar value.
*
* If the value is an object, it's properties are iterated through and passed back into this function with the name of each
* property extending the path. For instance, if the object contains the property "entry" and the path passed in was "/feed",
* this function is called with the value of the entry property and the path "/feed/entry".
*
* If the value is an array containing other arrays or objects, each element in the array is passed into this function with
* the rowIndex incremeneted for each element.
*
* If the value is an array containing only scalar values, those values are joined together and inserted into the data array as
* a single value.
*
* If the value is a scalar, the value is inserted directly into the data array.
*/
function parseData_(headers, data, path, rowIndex, value, query, options, includeFunc) {
var dataInserted = false;
if (isObject_(value)) {
for (key in value) {
if (parseData_(headers, data, path + "/" + key, rowIndex, value[key], query, options, includeFunc)) {
dataInserted = true;
}
}
} else if (Array.isArray(value) && isObjectArray_(value)) {
for (var i = 0; i < value.length; i++) {
if (parseData_(headers, data, path, rowIndex, value[i], query, options, includeFunc)) {
dataInserted = true;
rowIndex++;
}
}
} else if (!includeFunc || includeFunc(query, path, options)) {
// Handle arrays containing only scalar values
if (Array.isArray(value)) {
value = value.join();
}
// Insert new row if one doesn't already exist
if (!data[rowIndex]) {
data[rowIndex] = new Array();
}
// Add a new header if one doesn't exist
if (!headers[path] && headers[path] != 0) {
headers[path] = Object.keys(headers).length;
}
// Insert the data
data[rowIndex][headers[path]] = value;
dataInserted = true;
}
return dataInserted;
}
/**
* Parses the headers array and inserts it into the first row of the data array.
*/
function parseHeaders_(headers, data) {
data[0] = new Array();
for (key in headers) {
data[0][headers[key]] = key;
}
}
/**
* Applies the transform function for each element in the data array, going through each column of each row.
*/
function transformData_(data, options, transformFunc) {
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
transformFunc(data, i, j, options);
}
}
}
/**
* Returns true if the given test value is an object; false otherwise.
*/
function isObject_(test) {
return Object.prototype.toString.call(test) === '[object Object]';
}
/**
* Returns true if the given test value is an array containing at least one object; false otherwise.
*/
function isObjectArray_(test) {
for (var i = 0; i < test.length; i++) {
if (isObject_(test[i])) {
return true;
}
}
return false;
}
/**
* Returns true if the given query applies to the given path.
*/
function includeXPath_(query, path, options) {
if (!query) {
return true;
} else if (Array.isArray(query)) {
for (var i = 0; i < query.length; i++) {
if (applyXPathRule_(query[i], path, options)) {
return true;
}
}
} else {
return applyXPathRule_(query, path, options);
}
return false;
};
/**
* Returns true if the rule applies to the given path.
*/
function applyXPathRule_(rule, path, options) {
return path.indexOf(rule) == 0;
}
/**
* By default, this function transforms the value at the given row & column so it looks more like a normal data import. Specifically:
*
* - Data from parent JSON elements gets inherited to their child elements, so rows representing child elements contain the values
* of the rows representing their parent elements.
* - Values longer than 256 characters get truncated.
* - Values in row 0 (headers) have slashes converted to spaces, common prefixes removed and the resulting text converted to title
* case.
*
* To change this behavior, pass in one of these values in the options parameter:
*
* noInherit: Don't inherit values from parent elements
* noTruncate: Don't truncate values
* rawHeaders: Don't prettify headers
* debugLocation: Prepend each value with the row & column it belongs in
*/
function defaultTransform_(data, row, column, options) {
if (!data[row][column]) {
if (row < 2 || hasOption_(options, "noInherit")) {
data[row][column] = "";
} else {
data[row][column] = data[row-1][column];
}
}
if (!hasOption_(options, "rawHeaders") && row == 0) {
if (column == 0 && data[row].length > 1) {
removeCommonPrefixes_(data, row);
}
data[row][column] = toTitleCase_(data[row][column].toString().replace(/[\/\_]/g, " "));
}
if (!hasOption_(options, "noTruncate") && data[row][column]) {
data[row][column] = data[row][column].toString().substr(0, 256);
}
if (hasOption_(options, "debugLocation")) {
data[row][column] = "[" + row + "," + column + "]" + data[row][column];
}
}
/**
* If all the values in the given row share the same prefix, remove that prefix.
*/
function removeCommonPrefixes_(data, row) {
var matchIndex = data[row][0].length;
for (var i = 1; i < data[row].length; i++) {
matchIndex = findEqualityEndpoint_(data[row][i-1], data[row][i], matchIndex);
if (matchIndex == 0) {
return;
}
}
for (var i = 0; i < data[row].length; i++) {
data[row][i] = data[row][i].substring(matchIndex, data[row][i].length);
}
}
/**
* Locates the index where the two strings values stop being equal, stopping automatically at the stopAt index.
*/
function findEqualityEndpoint_(string1, string2, stopAt) {
if (!string1 || !string2) {
return -1;
}
var maxEndpoint = Math.min(stopAt, string1.length, string2.length);
for (var i = 0; i < maxEndpoint; i++) {
if (string1.charAt(i) != string2.charAt(i)) {
return i;
}
}
return maxEndpoint;
}
/**
* Converts the text to title case.
*/
function toTitleCase_(text) {
if (text == null) {
return null;
}
return text.replace(/\w\S*/g, function(word) { return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase(); });
}
/**
* Returns true if the given set of options contains the given option.
*/
function hasOption_(options, option) {
return options && options.indexOf(option) >= 0;
}
در ویرایشگر:
- “File” > “Save” را انتخاب کنید.
- کد “ImportJSON” یا چیزی مشابه را Title کنید.
- ویرایشگر Google Apps Script را ببندید و به Google Sheets خود بازگردید.
- در سلول انتخابی خود، =ImportJSON(“X”) را وارد کنید اما X را با URL API JSON خود جایگزین کنید.
- Enter را فشار دهید و دادههای JSON API شما به Google Sheet شما کشیده می شود.
اسکریپت، تابع، افزونه: کدام یک را انتخاب میکنید؟
هر روشی مزایا و معایب خاص خود را دارد. توابع Google Sheets رو متدهای اسکریپتهای Google Apps از نظر عملکرد محدود هستند، اما استفاده از آنها رایگان است. افزونههای Google Sheets ویژگیهای بهتری دارند، اما استفاده از آنها هزینه دارد. بسته به اینکه query شما چقدر ساده یا پیچیده است، میتوانید روش مناسب خود را انتخاب کنید.
نتیجهگیری
وارد کردن دادهها از منابع داده مختلف به Google Sheets ، آسانتر از چیزی است که فکر میکنید. توابع و ابزارهای فراوانی وجود دارند که میتوانید از آنها استفاده کنید و همیشه موارد بیشتری در دسترس هستند. اگر هیچ یک از روشهای بالا آن چیزی نیست که به دنبال آن هستید، میتوانید ابزار خود را با استفاده از Google Sheets API و Google Apps Script کدنویسی کنید. یا میتوانید شخصی را استخدام کنید تا این کد را برای شما بنویسد تا بتوانید دقیقاً همان چیزی را که به دنبالش هستید به دست بیاورید. این روزها استخدام یک فریلنسر از وب سایتی مانند Fiverr یا Upwork ، یک راه حل بسیار ارزان است.
منبع: hackernoon نویسنده: میلاد ماشاو

Leave feedback about this