Excel → LLM → Word
An early automation pipeline that turns spreadsheet rows into LLM-generated, formatted Word documents.
Built in 2023, before I had a paid LLM API key. Rather than copy-pasting into a chat window one row at a time, I automated the whole thing end to end — including the chat interface itself.
What it does.
Reads product rows from an Excel workbook, sends each one to an LLM for generated copy, and writes the result into a formatted Word document — one script, no manual steps in between.
There was no clean API to call at the time, so the script drives a real Chrome browser instead: it logs into a Gmail account, opens a hosted LLM chat site, types a prompt built from each row's data into the chat box, waits for the reply, and scrapes the response text back out of the page. python-docx then writes that text into a new Word document, one per product.
- Input
An Excel workbook read row by row with
openpyxl— no headers, just fixed columns. - Generation
Selenium (via
seleniumbase) drives a real browser session: Gmail login, then a hosted chat UI, one prompt per row. - Output
Each scraped reply is written into its own formatted
.docxwithpython-docx.
The real script.
This is the actual script, unedited, alongside the rest of the page — not a simplified version. Two things are changed from the source file: both Gmail credentials were already environment-variable reads before this page existed, and one string — the name of the third-party chat site it drives — is blacked out below, since this technique works by automating that site's own chat UI rather than a public API.
Everything else, including the hardcoded Windows file path and the positional (not header-based) column reads, is exactly what the script does.
from seleniumbase import Driver
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
import openpyxl
from openpyxl import load_workbook
from docx import Document
import os
# Load the Excel file
product_folder = "C:\Pyautomationattempt"
wrkbk = openpyxl.load_workbook("C:\Pyautomationattempt\ShopX-affiliate_tracking (1).xlsx")
printed_texts = set()
# Get the active sheet
sh = wrkbk.active
# Get the values from the specified column
column_values = [cell_obj.value for cell_obj in sh['K']]
names = [cell_obj.value for cell_obj in sh['J']]
username = os.environ["GMAIL_ADDRESS"] # set via environment variable / .env, do not hardcode
password = os.environ["GMAIL_APP_PASSWORD"] # set via environment variable / .env, do not hardcode
url = "https://accounts.google.com/v3/signin/identifier?continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&ifkv=ASKXGp3WpVELsnXuV3Lm7nSKpYOTpMtU8dUdYh9lqQiLo-D4_3jQ8YXgIWviCdD0_kY6e3IhKnub&rip=1&sacu=1&service=mail&flowName=GlifWebSignIn&flowEntry=ServiceLogin&dsh=S234983196%3A1702864568143473&theme=glif"
url2 = "https://redacted"
url3 = "https://redacted/ChatAndSearch"
driver = Driver(uc=True)
options = webdriver.ChromeOptions()
options.add_argument("start-maximized")
driver.get(url)
driver.maximize_window()
driver.find_element(By.ID, "identifierId").send_keys(username)
sleep(2)
driver.find_element(By.ID, "identifierNext").click()
sleep(3)
driver.find_element(By.NAME, "Passwd").send_keys(password)
sleep(3)
driver.find_element(By.ID, "passwordNext").click()
sleep(3)
driver.get(url2)
sleep(15)
driver.get(url3)
sleep(2)
for i, column_value in enumerate(column_values):
sleep(10)
driver.find_element(By.CLASS_NAME, "GrowingTextArea_textArea__ZWQbP").send_keys("write a paragraph about a random topic" + column_value)
sleep(3)
driver.find_element(By.CLASS_NAME, "ChatMessageSendButton_sendButton__4ZyI4").click()
sleep(10)
driver.refresh()
sleep(10)
allChats = driver.find_elements(By.XPATH, '//*[@id="__next"]/div[1]/div/main/div/div/div/div[2]/div/div[2]/div[2]/div[2]/div/div[1]/div/div/p')
for chat in allChats:
chat_text = chat.text
if chat_text not in printed_texts:
print(chat_text)
outputfile = Document()
p = outputfile.add_paragraph(chat_text)
outputfile.save( f"POEOUTPUTS_{names[i]}.docx")
printed_texts.add(chat_text)
11 iterative versions of this script exist in the project — a real record of incremental debugging over time, not one polished file. This is the last one.
- word.py
- word2.py
- word3.py
- word4.py
- word5.py
- word6.py
- word7.py
- word8.py
- word9.py
- word10.py
- word11.py
Verified run.
The real script needs a live Google login and a live browser session against a third-party site's current UI — not something to script unattended for a portfolio demo. So the Excel-reading and Word-writing mechanics were verified with the LLM step swapped for a clearly labeled stub, keeping the rest of the pipeline identical.
$ python3 demo_pipeline.py demo_products.xlsx demo_generated_catalog.docx
Processing 1: Atlas Standing Desk...
Processing 2: Meridian Task Lamp...
Processing 3: Northstar Travel Mug...
Processing 4: Basecamp Trail Backpack...
Processing 5: Fernwood Cutting Board...
Wrote 5 products to demo_generated_catalog.docx
Output confirmed as a real, valid .docx — reopened with python-docx and the generated paragraphs read back, not a stub file. Separately, running the real script (with no credentials supplied, as expected) reaches exactly the point documented above: it fails loading the hardcoded spreadsheet path, since that spreadsheet held a real affiliate tag and was removed from this copy — a known, undisturbed issue, not papered over.
What I learned.
Before I had access to a paid LLM API, I automated the free web chat interface directly instead — logging in, typing into the chat box, and scraping the DOM for the response. It's fragile compared to a real API, breaking whenever the site's UI changes, which is exactly why every later project in this portfolio calls an actual LLM API instead.
Iterating by copying the whole script into a new numbered file rather than editing one file in place is a real record of how I worked before I was disciplined about version control on personal projects — visible directly in the project's file list.
Status.
Private repository, run locally. No API, no login flow of its own, no hosted deployment — a personal automation script, not a product.