У меня такой код:
items = set() # use a set to not have duplicate items
a = requests.get(f"{BASE_URL}?{BASE_QUERIES}&cursor=*")
amount = a.json()["totalResults"] # in the range of 30 million
items.update(item["guid"].split("?")[0] for item in a.json()["items"]) # we only want the url before the query strings
cursor = a.json()["nextCursor"] # we have a cursor as each request only returns 100 items, the cursor shows use where to start
while len(items) < amount: # ensure we get all the items
try:
a = requests.get(f"{BASE_URL}?{BASE_QUERIES}&cursor={cursor}")
items.update(item["guid"].split("?")[0] for item in a.json()["items"])
except:
continue
try:
cursor = urllib.parse.quote(a.json()["nextCursor"])
except KeyError:
if len(items) == amount: # when we reach the final iteration the cursor will not be there
break
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36"
}
for link in items: # iterate over each item
for _ in range(3):
response = requests.get(link, headers=headers, stream=True)
response.raw.decode_content = True
parser = etree.HTMLParser()
tree = etree.parse(response.raw, parser)
setting = tree.xpath(
"/html/body/div[1]/div/div/main/div/div[2]/div/div[2]/div/div/div[1]/a/span[1]/text()"
)[0].strip()
try:
image = tree.xpath(
"/html/body/div[1]/div/div/main/div/div[2]/div/div[1]/div[1]/div/div/a/@href"
)[0] # most of the time this works
except:
try:
image = tree.xpath(
"/html/body/div[1]/div/div/main/div/div[2]/div/div[1]/div[1]/div[1]/a/@href"
)[0] # about 1 in 10 times the above fails and this works
except:
print(f"Image not found for link: {link}")
break
title = tree.findtext(".//title")
title_for_file = (
fr"{os.getcwd()}IMAGESIMAGE - " + "".join(c for c in title if c in valid_chars) + ".jpeg"
) # sometimes the file contains characters which aren't allowed in file names
description = "".join(
tree.xpath(
"/html/body/div[1]/div/div/main/div/div[3]/div[1]/div/div/div/div/p/text()"
)
)
if setting not in [x, y]: # we only want items which have a specific setting
break
try:
image_req = requests.get(image)
with open(title_for_file, "wb") as f:
f.write(image_req.content) # write the image to the directory
img = pyexiv2.Image(title_for_file)
metadata = {"Xmp.xmp.Title": title, "Xmp.xmp.Description": description} # edit the metadata
img.modify_xmp(metadata)
img.close()
break
except Exception as e:
print(
f"ERROR parsing image: {title_for_file}, it is most likely corrupt, "
f"retrying ({link}), "
f"error: {e}"
)
os.remove(title_for_file)
else:
print("ERROR more than 3 times moving to next")
В коде есть довольно подробные комментарии, объясняющие, что он делает, моя проблема в том, что на выполнение всех запросов на получение ссылок в коде требуется около 7 часов. items
, затем еще один (я не уверен на 100%, но, думаю, около 1500 часов), чтобы проверить каждое изображение и добавить метаданные. Есть что-нибудь в этом коде, который можно ускорить / улучшить, чтобы ускорить этот процесс?
Точно так же можно где-нибудь уменьшить использование памяти, если я подозреваемый это будет использовать много памяти с объемом данных, которые он анализирует.
РЕДАКТИРОВАТЬ
Возможным рассмотрением было бы использование Threading
чтобы загрузить сразу несколько изображений, как найти оптимальное количество потоков для одновременного запуска? Или, возможно, я бы начал каждый Thread
с небольшой задержкой, возможно 0,5 секунды?
1 ответ
Прежде чем вы начнете думать о таких необычных вещах, как распараллеливание этого кода, вам следует начать с помещения вашего кода в функции, каждая из которых выполняет одно действие.
def get_items(response):
# we only want the url before the query strings
# use a set to not have duplicate items
return {item["guid"].split("?")[0] for item in response["items"]}
def get_all_items():
url = f"{BASE_URL}?{BASE_QUERIES}"
response = requests.get(url, params={"cursor": "*"}).json()
amount = response["totalResults"] # in the range of 30 million
items = get_items(response)
while cursor := response.get("nextCursor"):
try:
response = requests.get(url, params={"cursor": cursor}).json()
items.update(get_items(response))
except Exception:
continue
if len(items) != amount:
raise ValueError("Did not get all items")
return items
Обратите внимание, что я изменил твою голую except
чтобы except Exception
, иначе пользователь нажмет Ctrl+C также будет проигнорирован.
Я также использовал новый оператор моржа, чтобы продолжить итерацию, пока nextCursor
дается в ответе.
HEADERS = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36"
}
ALLOWED_SETTINGS = {x, y}
def handle_link(link, retries=3):
for _ in range(retries):
if parse(link, ALLOWED_SETTINGS):
break
def parse(link, allowed_settings):
response = requests.get(link, headers=HEADERS, stream=True)
response.raw.decode_content = True
parser = etree.HTMLParser()
tree = etree.parse(response.raw, parser)
setting = get_setting(tree)
if setting not in allowed_settings: # we only want items which have a specific setting
return False
image = get_image(tree)
if image is None:
return False
title = ǵet_title(tree)
file_name = get_file_name(title)
description = get_description(tree)
return save_image(image, file_name,
{"Xmp.xmp.Title": title, "Xmp.xmp.Description": description})
Обратите внимание, что я прервал как можно скорее, нет необходимости продолжать синтаксический анализ, если setting
уже говорит вам, что вы выбросите результат.
def get_setting(tree):
return tree.xpath(
"/html/body/div[1]/div/div/main/div/div[2]/div/div[2]/div/div/div[1]/a/span[1]/text()"
)[0].strip()
def get_image(tree):
xpaths = ["/html/body/div[1]/div/div/main/div/div[2]/div/div[1]/div[1]/div/div/a/@href",
"/html/body/div[1]/div/div/main/div/div[2]/div/div[1]/div[1]/div[1]/a/@href"]
for xpath in xpaths:
try:
return tree.xpath(xpath)[0]
except Exception:
continue
print(f"Image not found for link: {link}")
def get_title(tree):
return tree.findtext(".//title")
def get_file_name(title):
return (
fr"{os.getcwd()}IMAGESIMAGE - " + "".join(c for c in title if c in valid_chars) + ".jpeg"
) # sometimes the file contains characters which aren't allowed in file names
def get_description(tree):
return "".join(
tree.xpath(
"/html/body/div[1]/div/div/main/div/div[3]/div[1]/div/div/div/div/p/text()"
)
)
def save_image(url, file_name, metadata):
try:
image_req = requests.get(url)
with open(file_name, "wb") as f:
f.write(image_req.content) # write the image to the directory
img = pyexiv2.Image(file_name)
img.modify_xmp(metadata)
img.close()
return True
except Exception as e:
print(
f"ERROR parsing image: {file_name}, it is most likely corrupt, "
f"error: {e}"
)
os.remove(file_name)
return False
Теперь вы можете начать думать о распараллеливании, поскольку на самом деле у вас есть инкапсулированная целевая функция для выполнения.
Поскольку теперь есть отдельные функции для получения некоторой информации, вы можете подумать об изменении способа анализа ссылок. Я лично предпочитаю использовать BeautifulSoup
над xpath
, поскольку использование селекторов CSS для классов или идентификаторов является IMO более читабельным, чем ваши длинные пути.
Обратите внимание, что в конце концов вы всегда будете ограничены доступным интернет-соединением. Если вам нужно загрузить 30 миллионов изображений размером 1 МБ при подключении к Интернету со скоростью 100 Мбит / с, он будет занять не менее 27 дней и если у вас соединение со скоростью 1 Гбит / с, это все равно займет почти три дня, независимо от того, как вы улучшите свой код. Если вы загружаете два изображения параллельно, каждое будет загружаться с половинной скоростью.