Skip to content

Instantly share code, notes, and snippets.

@JJTech0130
Created August 23, 2025 03:07
Show Gist options
  • Select an option

  • Save JJTech0130/042109e61a6f292405187c568a6c066b to your computer and use it in GitHub Desktop.

Select an option

Save JJTech0130/042109e61a6f292405187c568a6c066b to your computer and use it in GitHub Desktop.
Lookup Ford parts by scraping parts.ford.com
import requests
import json
import rich
from bs4 import BeautifulSoup, Tag
DEALER_STORE_ID=1405 # You can pretty much pick any dealer
CATALOG_ID=251 # Not sure
def get_wc_cookies():
# curl -L 'https://parts.ford.com/shop/AjaxManageUserVehicles' -H 'Content-Type: application/x-www-form-urlencoded' -d 'storeId=1405'
response = requests.post('https://parts.ford.com/shop/AjaxManageUserVehicles', headers={
'Content-Type': 'application/x-www-form-urlencoded'
}, data={
'storeId': DEALER_STORE_ID
})
# Check for cookies starting with WC_AUTHENTICATION and WC_USERACTIVITY
return {key: value for key, value in response.cookies.items() if key.startswith('WC_')}
def search_ui_single(term: str, cookies: dict):
# curl -L 'https://parts.ford.com/webapp/wcs/stores/servlet/en/us/SearchDisplay?storeId=1405&searchTerm=NU5Z-14G490-AT'
response = requests.get(f'https://parts.ford.com/webapp/wcs/stores/servlet/en/us/SearchDisplay?storeId={DEALER_STORE_ID}&searchTerm={term}', cookies=cookies)
redirected = response.url
# Make sure that we were redirected to https://parts.ford.com/shop/ (single part page rather than search results)
if not redirected.startswith('https://parts.ford.com/shop/'):
#raise ValueError(f"Expected redirect to parts.ford.com/shop/, got {redirected}")
raise ValueError(f"Part {term} not found")
# Extract the <script> inside <div id="Usages_pdp">
soup = BeautifulSoup(response.text, 'html.parser')
usages_div = soup.find('div', id='Usages_pdp')
if not isinstance(usages_div, Tag):
raise ValueError("Could not find Usages_pdp div in the response")
script_tag = usages_div.find('script')
if not isinstance(script_tag, Tag):
raise ValueError("Could not find script tag in Usages_pdp div")
script = script_tag.string
if not script:
raise ValueError("Could not find script content in script tag")
# Parse for usageItemsList = [ until ];
# Remove the first var usageItemsList = [];
script = script.replace('var usageItemsList = [];', '', 1)
data = script.split('usageItemsList = [', 1)[1].split('];', 1)[0]
if not data:
raise ValueError("Could not find usageItemsList in script")
# Parse as JSON
#print(data)
data = json.loads("[" + data + "]")
# Strip all of the values, recursively
def strip_values(obj):
if isinstance(obj, dict):
return {k: strip_values(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [strip_values(item) for item in obj]
elif isinstance(obj, str):
return obj.strip()
return obj
data = strip_values(data)
return data
def lookup_service_part(part: str):
# curl -L 'https://www.fordservicecontent.dealerconnection.com/Ford_Content/PublicationRuntimeRefreshPTS//wiring/GetServicePartNumber?book=null&bookType=svg&Terminalpartnumber=PU5T-14H474-CFE'
response = requests.get(f'https://www.fordservicecontent.dealerconnection.com/Ford_Content/PublicationRuntimeRefreshPTS//wiring/GetServicePartNumber?book=null&bookType=svg&Terminalpartnumber={part}')
if response.status_code != 200:
raise ValueError(f"Failed to retrieve service part information for {part}")
# {'ServicePart': {'ServicePartList': [{'Prefix': ' PU5Z', 'Base': ' 14G490', 'Suffix': 'AU ', 'ShortDesc': 'MODULE ', 'LongDesc': 'MODULE '}]}}
resp = response.json()
if resp.get('ServicePart', {}) is None:
return []
# Return a list of parts trimmed to eg "PU5Z-14G490-AU"
return [f"{part['Prefix'].strip()}-{part['Base'].strip()}-{part['Suffix'].strip()}" for part in resp.get('ServicePart', {}).get('ServicePartList', [])]
def main(part: str, verbose: bool):
# Try looking up the service part
service_parts = lookup_service_part(part)
if len(service_parts) > 0:
rich.print(f"Found service parts for {part}: {service_parts}")
part = service_parts[0]
cookies = get_wc_cookies()
#print(cookies)
search_results = search_ui_single(part, cookies)
for search_result in search_results:
assert isinstance(search_result, dict)
if search_result.get("longDescription"):
for l in search_result["longDescription"]:
rich.print(l)
else:
rich.print(search_result.get("displayDescription"))
if search_result.get("xillustration_full") and isinstance(search_result["xillustration_full"], str):
rich.print("https://parts.ford.com/images/section-images/" + search_result["xillustration_full"], end=' ')
if search_result.get("xads_3callout") and isinstance(search_result["xads_3callout"], str):
rich.print("(" + search_result["xads_3callout"] + ")")
else:
rich.print()
rich.print()
if verbose:
rich.print(search_results)
if __name__ == "__main__":
# Parse part from argument
import argparse
parser = argparse.ArgumentParser(description="Find Ford parts")
parser.add_argument("part", type=str, help="The part number to search for")
parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
args = parser.parse_args()
main(args.part, args.verbose)
@JJTech0130
Copy link
Author

Example output:
Screenshot 2025-08-22 at 11 08 47 PM

@JJTech0130
Copy link
Author

This is now broken because Ford completely redesigned the parts site, but honestly the new site is so much better I'm not even complaining.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment